go 33 lines · 1 tab

OpenTelemetry tracer provider with ratio-based sampling

Leah Thompson Jan 2026
1 tab
package observability

import (
  "context"

  "go.opentelemetry.io/otel"
  "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
  "go.opentelemetry.io/otel/propagation"
  "go.opentelemetry.io/otel/sdk/resource"
  sdktrace "go.opentelemetry.io/otel/sdk/trace"
  semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
)

func InitTracing(ctx context.Context, serviceName string) (*sdktrace.TracerProvider, error) {
  exp, err := otlptracegrpc.New(ctx)
  if err != nil {
    return nil, err
  }
  res, err := resource.New(ctx, resource.WithAttributes(semconv.ServiceName(serviceName)))
  if err != nil {
    return nil, err
  }

  tp := sdktrace.NewTracerProvider(
    sdktrace.WithBatcher(exp),
    sdktrace.WithResource(res),
    sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.1))),
  )

  otel.SetTracerProvider(tp)
  otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
  return tp, nil
}
1 file · go Explain with highlit

To get useful traces, you need propagation and a real exporter. I set a global TextMapPropagator (TraceContext + Baggage) so inbound headers connect spans across services. Then I configure an OTLP exporter and a batch span processor so tracing overhead stays low. I also explicitly set a sampler: ParentBased(TraceIDRatioBased(0.1)) is a common starting point that respects upstream sampling and keeps costs predictable. The other key piece is resource attributes like service.name, which is how traces are grouped in most backends. Once this is initialized, you can start spans in handlers with otel.Tracer("...").Start(ctx, ...) and get end-to-end visibility without special log parsing.


Related snips

Share this code

Here's the card — post it anywhere.

OpenTelemetry tracer provider with ratio-based sampling — share card
Link copied