// Package tracer bootstraps the OpenTelemetry SDK for a service and exports // traces over OTLP/HTTP (default endpoint localhost:4318, overridable via the // OTEL_EXPORTER_OTLP_ENDPOINT environment variable). // // Typical usage: // // if err := tracer.Init("api-gateway"); err != nil { log.Fatal(err) } // defer tracer.Shutdown(context.Background()) package tracer import ( "context" "fmt" "os" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "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.24.0" "go.opentelemetry.io/otel/trace" ) // tp is the process-wide TracerProvider created by Init and torn down by // Shutdown. var tp *sdktrace.TracerProvider // Init initializes the OpenTelemetry SDK with an OTLP HTTP exporter and // registers it as the global TracerProvider. The service is identified by // serviceName on all exported spans. func Init(serviceName string) error { endpoint := os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT") if endpoint == "" { endpoint = "localhost:4318" } exp, err := otlptracehttp.New( context.Background(), otlptracehttp.WithEndpoint(endpoint), otlptracehttp.WithInsecure(), ) if err != nil { return fmt.Errorf("tracer: creating OTLP exporter: %w", err) } res, err := resource.Merge( resource.Default(), resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceName(serviceName), ), ) if err != nil { return fmt.Errorf("tracer: building resource: %w", err) } tp = sdktrace.NewTracerProvider( sdktrace.WithBatcher(exp), sdktrace.WithResource(res), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{}, )) return nil } // Shutdown flushes pending spans and releases resources held by the // TracerProvider. It is a no-op when Init was never called. The provided // context bounds the flush to 5 seconds. func Shutdown(ctx context.Context) error { if tp == nil { return nil } ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() if err := tp.Shutdown(ctx); err != nil { return fmt.Errorf("tracer: shutting down provider: %w", err) } return nil } // FromContext returns the active span from ctx. When no span is active the // returned span is a no-op, so callers may safely record attributes or events // without nil checks. func FromContext(ctx context.Context) trace.Span { return trace.SpanFromContext(ctx) }