Observability with OpenTelemetry

This guide walks through building observability into a service from scratch using OpenTelemetry. By the end you will have traces, metrics, and logs flowing into a collector and visualized in Grafana.

Part 1: Why Observability Matters

Logs tell you what happened. Metrics tell you how much. Traces tell you why. Together they give you the full picture when something breaks at 3 AM.

OpenTelemetry is the industry standard for instrumentation. It is vendor-neutral, has SDKs for every major language, and integrates with every major backend (Jaeger, Prometheus, Datadog, Grafana Cloud).

Part 2: Your First Trace

Install the Go SDK:

go get go.opentelemetry.io/otel
go get go.opentelemetry.io/otel/sdk

Create a tracer provider and wrap your HTTP handler:

func main() {
    tp := initTracerProvider()
    defer tp.Shutdown(context.Background())

    mux := http.NewServeMux()
    mux.HandleFunc("/api/users", usersHandler)

    handler := otelhttp.NewHandler(mux, "server")
    http.ListenAndServe(":8080", handler)
}

Every incoming request now generates a span with method, route, status code, and duration.

Part 3: Adding Custom Spans

Automatic instrumentation covers the edges. For the interesting parts — database queries, cache lookups, business logic — add manual spans:

func getUser(ctx context.Context, id string) (*User, error) {
    ctx, span := tracer.Start(ctx, "getUser")
    defer span.End()

    span.SetAttributes(attribute.String("user.id", id))

    // ...
}

Part 4: Metrics and the Collector

Coming soon — configuring the OTel Collector, Prometheus scraping, and Grafana dashboards.