// Package telemetry wires OpenTelemetry tracing and metrics. package telemetry import ( "context" "strings" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/otel/sdk/trace" ) // Options configures the telemetry providers. type Options struct { Enabled bool Endpoint string ServiceName string Environment string Version string } // Provider owns the configured OpenTelemetry providers. type Provider struct { Enabled bool shutdown []func(context.Context) error } // Setup installs global tracer and meter providers. When disabled, the // OpenTelemetry no-op providers remain in place and Shutdown is a no-op. func Setup(ctx context.Context, opts Options) (*Provider, error) { p := &Provider{Enabled: opts.Enabled} if !opts.Enabled { return p, nil } serviceName := opts.ServiceName if serviceName == "" { serviceName = "kereru-forms" } endpoint := opts.Endpoint if endpoint == "" { endpoint = "localhost:4318" } res, err := resource.New(ctx, resource.WithAttributes( attribute.String("service.name", serviceName), attribute.String("service.version", opts.Version), attribute.String("deployment.environment", opts.Environment), ), ) if err != nil { return nil, err } traceExporter, err := otlptracehttp.New(ctx, traceOptions(endpoint)...) if err != nil { return nil, err } tp := trace.NewTracerProvider( trace.WithBatcher(traceExporter), trace.WithResource(res), ) otel.SetTracerProvider(tp) p.shutdown = append(p.shutdown, tp.Shutdown) metricExporter, err := otlpmetrichttp.New(ctx, metricOptions(endpoint)...) if err != nil { _ = tp.Shutdown(ctx) return nil, err } mp := metric.NewMeterProvider( metric.WithReader(metric.NewPeriodicReader(metricExporter, metric.WithInterval(15*time.Second))), metric.WithResource(res), ) otel.SetMeterProvider(mp) p.shutdown = append(p.shutdown, mp.Shutdown) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{}, )) return p, nil } // Shutdown flushes telemetry. Safe to call when telemetry is disabled. func (p *Provider) Shutdown(ctx context.Context) error { if p == nil { return nil } var firstErr error for _, fn := range p.shutdown { if err := fn(ctx); err != nil && firstErr == nil { firstErr = err } } return firstErr } func traceOptions(endpoint string) []otlptracehttp.Option { if strings.Contains(endpoint, "://") { return []otlptracehttp.Option{otlptracehttp.WithEndpointURL(endpoint)} } return []otlptracehttp.Option{ otlptracehttp.WithEndpoint(endpoint), otlptracehttp.WithInsecure(), } } func metricOptions(endpoint string) []otlpmetrichttp.Option { if strings.Contains(endpoint, "://") { return []otlpmetrichttp.Option{otlpmetrichttp.WithEndpointURL(endpoint)} } return []otlpmetrichttp.Option{ otlpmetrichttp.WithEndpoint(endpoint), otlpmetrichttp.WithInsecure(), } }