// Package observability provides OpenTelemetry tracing setup and configuration. package observability import ( "context" "fmt" "os" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "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.26.0" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" ) // Config holds OpenTelemetry configuration. type Config struct { Enabled bool ServiceName string ServiceVersion string Environment string OTLPEndpoint string } // InitTracer initializes OpenTelemetry tracing. func InitTracer(ctx context.Context, cfg Config) (trace.TracerProvider, error) { if !cfg.Enabled { // Return a no-op tracer provider return noop.NewTracerProvider(), nil } // Create resource with service information res, err := resource.New(ctx, resource.WithAttributes( semconv.ServiceNameKey.String(cfg.ServiceName), semconv.ServiceVersionKey.String(cfg.ServiceVersion), semconv.DeploymentEnvironmentKey.String(cfg.Environment), ), ) if err != nil { return nil, fmt.Errorf("failed to create resource: %w", err) } var exporter sdktrace.SpanExporter if cfg.Environment == "production" && cfg.OTLPEndpoint != "" { // Production: export to OTLP collector exporter, err = otlptracehttp.New(ctx, otlptracehttp.WithEndpoint(cfg.OTLPEndpoint), otlptracehttp.WithInsecure(), // Use WithTLSClientConfig for secure connections ) if err != nil { return nil, fmt.Errorf("failed to create OTLP exporter: %w", err) } } else { // Development: export to stdout exporter, err = stdouttrace.New( stdouttrace.WithPrettyPrint(), stdouttrace.WithWriter(os.Stdout), ) if err != nil { return nil, fmt.Errorf("failed to create stdout exporter: %w", err) } } // Create tracer provider tp := sdktrace.NewTracerProvider( sdktrace.WithBatcher(exporter), sdktrace.WithResource(res), sdktrace.WithSampler(sdktrace.AlwaysSample()), // Sample all traces in dev, can be adjusted for prod ) // Set global tracer provider otel.SetTracerProvider(tp) // Set global propagator for trace context otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{}, )) return tp, nil } // ShutdownTracer gracefully shuts down the tracer provider. func ShutdownTracer(ctx context.Context, tp trace.TracerProvider) error { if ttp, ok := tp.(*sdktrace.TracerProvider); ok { return ttp.Shutdown(ctx) } return nil }