Adding Observability to a Go App Using OpenTelemetry and SigNoz


Hey folks! 👋
Ever screamed “WHY is this app slow?!” while staring at your terminal like it betrayed you?Yeah, we’ve all been there.
500 errors. Logs missing the real issue. Dashboards saying “everything’s fine” when your users say otherwise.It’s not magic, it’s missing observability. So today, we’re fixing that with OpenTelemetry+ SigNoz (self-hosted).
Grab your coffee , your favorite terminal theme, and let’s go from “what just happened?” to “oh, I see exactly where it broke” all in Go.
Why Observability Now?
Let’s be honest. Logs in files and Prometheus metrics can only take you so far.
You’re probably here because:
You’ve had multiple outages and postmortems pointed to "lack of visibility"
Developers are tired of playing whack-a-mole with flaky services
You’re looking for an open source, production grade APM solution
Enter OpenTelemetry + SigNoz.
Meet the Heroes: OpenTelemetry and SigNoz
OpenTelemetry: The Universal Spy Kit
OpenTelemetry (or just OTel) is a CNCF project that lets you collect logs, metrics and traces from your app. It’s vendor-neutral, cloud-native, and growing like crazy.
Think of OpenTelemetry as the instrumentation toolkit. it captures data inside your app.
You install a Go SDK, sprinkle in a few lines of code, and it starts recording data like:
how long requests take,
what endpoints are hit,
where errors happen, and more.
Cool, right?
SigNoz: Your Observability Command Center
SigNoz is the dashboard where all your app’s secrets come together.
It’s open-source, self-hostable and built to give you a seamless experience:
View traces like a Jaeger
Plot metrics like Prometheus + Grafana
Explore logs like Loki
All-in-one, powered by ClickHouse under the hood.
Why SigNoz instead of stitching tools together?
No Prometheus + Grafana + Jaeger + Loki madness.
Works out of the box with OpenTelemetry.
Beautiful UI. Self-hosted or cloud — your choice.
SigNoz Architecture
At a high level, here’s how SigNoz works:
Collector → receives traces, metrics, logs
Query Service → talks to ClickHouse
Frontend → shows everything in a nice dashboard
You can run all of this using Docker in 5 minutes.
Let’s Build an Observable Go App
Now let’s make all this practical. We’ll build a small Go HTTP app, instrument it using OpenTelemetry, and ship data to self-hosted SigNoz. (I have created a sample GitHub repo with all the code and prompt files mentioned in this blog. Check out the repo here)
Step 1: Build a Tiny Go App
// main.go
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Welcome to my observable blog! 📝")
})
http.HandleFunc("/post", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Here's a blog post!")
})
log.Println("Blog running on http://localhost:8080")
http.ListenAndServe(":8080", nil)
}
You know the drill:
go run main.go
Step 2: Add OpenTelemetry SDK
go get go.opentelemetry.io/otel
go get go.opentelemetry.io/otel/sdk
go get go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
Create a new file otel.go
:
package main
import (
"context"
"log"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
)
func InitTracer() func(context.Context) error {
ctx := context.Background()
exporter, err := otlptracehttp.New(ctx,
otlptracehttp.WithInsecure(),
otlptracehttp.WithEndpoint("localhost:4318"),
)
if err != nil {
log.Fatalf("Failed to create OTLP exporter: %v", err)
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceName("go-blog"),
)),
)
otel.SetTracerProvider(tp)
return tp.Shutdown
}
Now wrap handlers in your main.go
:
// main.go
package main
import (
"context"
"fmt"
"log"
"net/http"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
func main() {
shutdown := InitTracer()
defer shutdown(context.Background())
http.Handle("/", otelhttp.NewHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Welcome to my observable blog! 📝")
}), "HomeHandler"))
http.Handle("/post", otelhttp.NewHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Here's a blog post!")
}), "PostHandler"))
log.Println("🚀 Blog running on http://localhost:8080")
http.ListenAndServe(":8080", nil)
}
Step 3: Self-host SigNoz Locally
git clone https://github.com/SigNoz/signoz.git && cd signoz/deploy/docker
./install.sh
After it spins up, open:
👉 http://localhost:3301
You’ll see the beautiful SigNoz dashboard.
Step 4: Explore Your Data
Visit:
Then check SigNoz → Traces.
Boom! You’ll see:
HomeHandler
PostHandler
With timing, performance and trace graphs!
What Else Can You Do with SigNoz?
If you’ve made it this far, awesome. You’ve got your Go app sending traces and metrics to SigNoz. But honestly? That’s just the beginning.
There’s so much more you can explore:
Add your own custom spans and really trace what matters
Build dashboards to keep an eye on latency or errors
Try setting up alerts so you know when things go sideways
Hook up logs (yep, log support is here too in beta!)
Plug in other services like Node.js, Python, Java, even mobile
Or deploy SigNoz on Kubernetes if you’re going full scale
Basically, if you’re into visibility, performance, or just not getting paged at 3AM with no idea what broke. SigNoz is worth diving deeper into.
Get Started
Subscribe to my newsletter
Read articles from Harshal Rembhotkar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Harshal Rembhotkar
Harshal Rembhotkar
I do Dev, I do Ops, and I do it (most days).