package telemetry import ( "context" "errors" "log/slog" "strings" "go.opentelemetry.io/contrib/bridges/otelslog" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/log/global" sdklog "go.opentelemetry.io/otel/sdk/log" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/otel/sdk/trace" ) func newResource(serviceName string) *resource.Resource { return resource.NewWithAttributes( resource.Default().SchemaURL(), attribute.String("service.name", serviceName), ) } // Init sets up OpenTelemetry tracing, metrics, and logs via OTLP Push. // It returns a shutdown function that should be deferred in main. func Init(ctx context.Context, endpoint string, serviceName string) (func(context.Context) error, error) { res := newResource(serviceName) var shutdownFuncs []func(context.Context) error shutdown := func(ctx context.Context) error { var err error for _, fn := range shutdownFuncs { if fnErr := fn(ctx); fnErr != nil { err = errors.Join(err, fnErr) } } return err } var traceOpts []otlptracehttp.Option var metricOpts []otlpmetrichttp.Option var logOpts []otlploghttp.Option if endpoint != "" { endpoint = strings.TrimSuffix(endpoint, "/") traceOpts = append(traceOpts, otlptracehttp.WithEndpointURL(endpoint+"/v1/traces")) metricOpts = append(metricOpts, otlpmetrichttp.WithEndpointURL(endpoint+"/v1/metrics")) logOpts = append(logOpts, otlploghttp.WithEndpointURL(endpoint+"/v1/logs")) } else { // Default to insecure localhost if no endpoint is specified traceOpts = append(traceOpts, otlptracehttp.WithInsecure()) metricOpts = append(metricOpts, otlpmetrichttp.WithInsecure()) logOpts = append(logOpts, otlploghttp.WithInsecure()) } // Trace setup traceExporter, err := otlptracehttp.New(ctx, traceOpts...) if err != nil { return shutdown, err } tp := trace.NewTracerProvider( trace.WithBatcher(traceExporter), trace.WithResource(res), ) otel.SetTracerProvider(tp) shutdownFuncs = append(shutdownFuncs, tp.Shutdown) // Metrics setup metricExporter, err := otlpmetrichttp.New(ctx, metricOpts...) if err != nil { return shutdown, err } mp := metric.NewMeterProvider( metric.WithReader(metric.NewPeriodicReader(metricExporter)), metric.WithResource(res), ) otel.SetMeterProvider(mp) shutdownFuncs = append(shutdownFuncs, mp.Shutdown) // Logs setup logExporter, err := otlploghttp.New(ctx, logOpts...) if err != nil { return shutdown, err } lp := sdklog.NewLoggerProvider( sdklog.WithProcessor(sdklog.NewBatchProcessor(logExporter)), sdklog.WithResource(res), ) global.SetLoggerProvider(lp) shutdownFuncs = append(shutdownFuncs, lp.Shutdown) // Route slog to OpenTelemetry slog.SetDefault(otelslog.NewLogger(serviceName)) return shutdown, nil }