package telemetry import ( "context" "errors" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" otlploggrpc "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc" otlpmetricgrpc "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" otlptracegrpc "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/log/global" "go.opentelemetry.io/otel/propagation" sdklog "go.opentelemetry.io/otel/sdk/log" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.24.0" ) // Config holds OTel bootstrap configuration. type Config struct { // ServiceName identifies the service in traces, metrics, and logs. ServiceName string `env:"OTEL_SERVICE_NAME,required"` // ServiceVersion is the deployed version (e.g. "1.4.2"). ServiceVersion string `env:"OTEL_SERVICE_VERSION" envDefault:"unknown"` // Environment is the deployment environment (e.g. "production", "staging"). Environment string `env:"OTEL_ENVIRONMENT" envDefault:"development"` // OTLPEndpoint is the OTLP gRPC collector address (e.g. "alloy:4317"). OTLPEndpoint string `env:"OTEL_EXPORTER_OTLP_ENDPOINT,required"` // OTLPInsecure disables TLS for the OTLP connection. Set true in development. OTLPInsecure bool `env:"OTEL_EXPORTER_OTLP_INSECURE" envDefault:"false"` } // New bootstraps the full OTel SDK: // - TracerProvider → OTLP gRPC → Grafana Alloy → Tempo // - MeterProvider → OTLP gRPC → Grafana Alloy → Mimir // - LoggerProvider → OTLP gRPC → Grafana Alloy → Loki // // Sets the three OTel globals so all micro-libs using the global API // auto-instrument without importing this module. // // The returned shutdown function flushes all exporters and must be called // before process exit (defer it in main or wire it into the launcher). // Returns (shutdown, nil) on success, (nil, err) on failure. func New(ctx context.Context, cfg Config) (func(context.Context) error, error) { res := newResource(cfg) // --- TracerProvider (traces → Tempo) --- traceOpts := []otlptracegrpc.Option{ otlptracegrpc.WithEndpoint(cfg.OTLPEndpoint), } if cfg.OTLPInsecure { traceOpts = append(traceOpts, otlptracegrpc.WithInsecure()) } traceExporter, err := otlptracegrpc.New(ctx, traceOpts...) if err != nil { return nil, err } tp := sdktrace.NewTracerProvider( sdktrace.WithBatcher(traceExporter), sdktrace.WithResource(res), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{}, )) // --- MeterProvider (metrics → Mimir) --- metricOpts := []otlpmetricgrpc.Option{ otlpmetricgrpc.WithEndpoint(cfg.OTLPEndpoint), } if cfg.OTLPInsecure { metricOpts = append(metricOpts, otlpmetricgrpc.WithInsecure()) } metricExporter, err := otlpmetricgrpc.New(ctx, metricOpts...) if err != nil { _ = tp.Shutdown(ctx) return nil, err } mp := sdkmetric.NewMeterProvider( sdkmetric.WithReader(sdkmetric.NewPeriodicReader(metricExporter)), sdkmetric.WithResource(res), ) otel.SetMeterProvider(mp) // --- LoggerProvider (logs → Loki) --- logOpts := []otlploggrpc.Option{ otlploggrpc.WithEndpoint(cfg.OTLPEndpoint), } if cfg.OTLPInsecure { logOpts = append(logOpts, otlploggrpc.WithInsecure()) } logExporter, err := otlploggrpc.New(ctx, logOpts...) if err != nil { _ = tp.Shutdown(ctx) _ = mp.Shutdown(ctx) return nil, err } lp := sdklog.NewLoggerProvider( sdklog.WithProcessor(sdklog.NewBatchProcessor(logExporter)), sdklog.WithResource(res), ) global.SetLoggerProvider(lp) shutdown := func(ctx context.Context) error { return errors.Join( tp.Shutdown(ctx), mp.Shutdown(ctx), lp.Shutdown(ctx), ) } return shutdown, nil } // newResource builds an OTel resource with service identity and environment attributes. func newResource(cfg Config) *resource.Resource { r, _ := resource.Merge( resource.Default(), resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceName(cfg.ServiceName), semconv.ServiceVersion(cfg.ServiceVersion), attribute.String("deployment.environment", cfg.Environment), ), ) return r }