package telemetry import ( "context" "errors" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/log/global" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/trace" ) // SetupOTelSDK bootstraps the OpenTelemetry pipeline (propagator + traces + // metrics, and an opt-in logger — see NewLoggerProvider). It is shared by every // service main package; the service name comes from OTEL_RESOURCE_ATTRIBUTES, so // the same setup works everywhere. If it returns no error, call the returned // shutdown for proper cleanup. func SetupOTelSDK(ctx context.Context) (func(context.Context) error, error) { var shutdownFuncs []func(context.Context) error var err error // shutdown calls the registered cleanup functions, joining their errors. shutdown := func(ctx context.Context) error { var err error for _, fn := range shutdownFuncs { err = errors.Join(err, fn(ctx)) } shutdownFuncs = nil return err } // handleErr runs shutdown and surfaces all errors. handleErr := func(inErr error) { err = errors.Join(inErr, shutdown(ctx)) } otel.SetTextMapPropagator(newPropagator()) tracerProvider, err := newTracerProvider() if err != nil { handleErr(err) return shutdown, err } shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown) otel.SetTracerProvider(tracerProvider) meterProvider, err := newMeterProvider() if err != nil { handleErr(err) return shutdown, err } shutdownFuncs = append(shutdownFuncs, meterProvider.Shutdown) otel.SetMeterProvider(meterProvider) // Logger provider is opt-in via OTEL_LOGS_ENABLED; nil means logs are off. // Traces + metrics above are always on. loggerProvider, err := NewLoggerProvider(ctx) if err != nil { handleErr(err) return shutdown, err } if loggerProvider != nil { shutdownFuncs = append(shutdownFuncs, loggerProvider.Shutdown) global.SetLoggerProvider(loggerProvider) } return shutdown, err } func newPropagator() propagation.TextMapPropagator { return propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{}, ) } func newTracerProvider() (*trace.TracerProvider, error) { traceExporter, err := otlptracegrpc.New(context.Background()) if err != nil { return nil, err } return trace.NewTracerProvider( trace.WithBatcher(traceExporter, trace.WithBatchTimeout(5*time.Second)), ), nil } func newMeterProvider() (*metric.MeterProvider, error) { exporter, err := otlpmetricgrpc.New(context.Background()) if err != nil { return nil, err } return metric.NewMeterProvider(metric.WithReader(metric.NewPeriodicReader(exporter, metric.WithInterval(time.Second*15)))), nil }