Skip to content

OpenTelemetry Go

ObservabilityMonitoring / MetricsGo

What it is

The Go SDK for OpenTelemetry — vendor-neutral distributed tracing, metrics and logs that can be exported to Jaeger, Tempo, Datadog or any OTLP backend.

Configure a TracerProvider with an exporter, then create spans around meaningful operations. Context propagation carries the trace across service boundaries.

Installation

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

Getting started

The smallest useful thing you can do with it, and what each part means.

Provider setup and a span
exporter, err := otlptracegrpc.New(ctx)
tp := sdktrace.NewTracerProvider(
    sdktrace.WithBatcher(exporter),
    sdktrace.WithResource(resource.NewWithAttributes(
        semconv.SchemaURL,
        semconv.ServiceName("library-api"),
    )),
    sdktrace.WithSampler(sdktrace.TraceIDRatioBased(0.1)), // sample 10%
)
otel.SetTracerProvider(tp)
defer tp.Shutdown(ctx) // flush buffered spans

tracer := otel.Tracer("library-api")

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

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

    book, err := store.Find(ctx, id) // child spans attach via ctx
    if err != nil {
        span.RecordError(err)
        span.SetStatus(codes.Error, err.Error())
        return nil, err
    }
    return book, nil
}
The returned ctx is what links child spans to this one — passing the original context instead is the most common instrumentation bug, and produces flat, useless traces.

Advanced usage

Where the library earns its place over a simpler alternative.

Propagating across services
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
    propagation.TraceContext{},
    propagation.Baggage{},
))

// Server: otelhttp extracts the incoming trace context.
handler := otelhttp.NewHandler(mux, "library-api")

// Client: otelhttp injects it into outgoing headers.
client := http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)}
Without a propagator, each service produces its own disconnected trace. This pair of wrappers is what turns per-service spans into one end-to-end request timeline.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

Traces are flat, with no child spans
A parent context was not threaded through. Use the ctx returned by tracer.Start for all downstream calls.
No spans reach the backend
The batch processor buffers. Call tp.Shutdown or ForceFlush before exit, and verify the exporter endpoint.

Best practices

  • Always use the context returned by tracer.Start, or your spans will not nest.
  • Call defer span.End() immediately after starting a span.
  • Sample in production — tracing every request is expensive at volume.
  • Shut the provider down on exit so buffered spans are flushed.

Background

Why it exists, and what it was reacting to.

OpenTelemetry merged the competing OpenTracing and OpenCensus projects into one standard, so instrumentation is written once and the backend becomes a configuration choice.