package monitoring import ( "context" "errors" "fmt" "net/http" "os" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" "go.opentelemetry.io/otel/log/global" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/log" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/trace" ) // SetupOTelSDK bootstraps the OpenTelemetry pipeline. func SetupOTelSDK(ctx context.Context, enabled bool, serviceName, buildVersion, environment string) (func(context.Context) error, error) { if os.Getenv("OTEL_RESOURCE_ATTRIBUTES") == "" { if err := os.Setenv("OTEL_RESOURCE_ATTRIBUTES", fmt.Sprintf("service.name=%s,service.version=%s,service.environment=%s", serviceName, buildVersion, environment)); err != nil { return func(context.Context) error { return nil }, err } } var shutdownFuncs []func(context.Context) error if !enabled { return func(context.Context) error { return nil }, nil } shutdown := func(ctx context.Context) error { var err error for _, fn := range shutdownFuncs { err = errors.Join(err, fn(ctx)) } shutdownFuncs = nil return err } // handleErr calls shutdown for cleanup and makes sure that all errors are returned. handleErr := func(inErr error) (func(context.Context) error, error) { return nil, errors.Join(inErr, shutdown(ctx)) } // Set up the propagator. prop := propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{}, ) otel.SetTextMapPropagator(prop) traceExporter, err := otlptracehttp.New(ctx) if err != nil { return handleErr(err) } shutdownFuncs = append(shutdownFuncs, traceExporter.Shutdown) tracerProvider := trace.NewTracerProvider( trace.WithBatcher(traceExporter, trace.WithBatchTimeout(5*time.Second)), ) shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown) otel.SetTracerProvider(tracerProvider) logExporter, err := stdoutlog.New() if err != nil { return handleErr(err) } processor := log.NewSimpleProcessor(logExporter) logProvider := log.NewLoggerProvider(log.WithProcessor(processor)) global.SetLoggerProvider(logProvider) shutdownFuncs = append(shutdownFuncs, logProvider.Shutdown) exp, err := otlpmetrichttp.New(ctx) if err != nil { return handleErr(err) } meterProvider := metric.NewMeterProvider(metric.WithReader(metric.NewPeriodicReader(exp))) shutdownFuncs = append(shutdownFuncs, meterProvider.Shutdown) otel.SetMeterProvider(meterProvider) return shutdown, err } func Handler(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := otel.GetTextMapPropagator().Extract(r.Context(), propagation.HeaderCarrier(r.Header)) spanCtx, s := StartSpan(ctx, "http") defer s.Finish() h.ServeHTTP(w, r.WithContext(spanCtx)) }) }