// Package metrics provides OpenTelemetry-native metrics with OTLP export support. // // This package implements a metrics system using the OpenTelemetry metrics data model // with OTLP export capabilities. It's designed for new applications that want to use // structured metrics export to observability backends. // // Key features: // - OpenTelemetry native metric types (Counter, Histogram, Gauge, etc.) // - OTLP export for sending metrics to observability backends // - Resource detection and correlation with traces/logs // - Graceful handling when OTLP configuration is not available // // Example usage: // // // Initialize metrics along with tracing // shutdown, err := tracing.InitTracer(ctx, cfg) // if err != nil { // log.Fatal(err) // } // defer shutdown(ctx) // // // Get a meter and create instruments // meter := metrics.GetMeter("my-service") // counter, _ := meter.Int64Counter("requests_total") // counter.Add(ctx, 1, metric.WithAttributes(attribute.String("method", "GET"))) package metrics import ( "context" "log/slog" "sync" "time" "go.ntppool.org/common/internal/tracerconfig" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/metric" sdkmetric "go.opentelemetry.io/otel/sdk/metric" ) var ( meterProvider metric.MeterProvider setupOnce sync.Once setupErr error ) // Setup initializes the OpenTelemetry metrics provider with OTLP export. // This function uses the configuration stored by the tracing package and // creates a metrics provider that exports to the same OTLP endpoint. // // The function is safe to call multiple times - it will only initialize once. // If tracing configuration is not available, it returns a no-op provider that // doesn't export metrics. // // Returns an error only if there's a configuration problem. Missing tracing // configuration is handled gracefully with a warning log. func Setup(ctx context.Context) error { setupOnce.Do(func() { setupErr = initializeMetrics(ctx) }) return setupErr } // GetMeter returns a named meter for creating metric instruments. // The meter uses the configured metrics provider, or the global provider // if metrics haven't been set up yet. // // This is the primary entry point for creating metric instruments in your application. func GetMeter(name string, opts ...metric.MeterOption) metric.Meter { if meterProvider == nil { // Return the global provider as fallback (no-op if not configured) return otel.GetMeterProvider().Meter(name, opts...) } return meterProvider.Meter(name, opts...) } // initializeMetrics sets up the OpenTelemetry metrics provider with OTLP export. func initializeMetrics(ctx context.Context) error { log := slog.Default() // Check if tracing configuration is available cfg, configCtx, factory := tracerconfig.GetMetricExporter() if cfg == nil || configCtx == nil || factory == nil { log.Warn("metrics setup: tracing configuration not available, using no-op provider") // Set the global provider as fallback - metrics just won't be exported meterProvider = otel.GetMeterProvider() return nil } // Create OTLP metrics exporter exporter, err := factory(ctx, cfg) if err != nil { log.Error("metrics setup: failed to create OTLP exporter", "error", err) // Fall back to global provider meterProvider = otel.GetMeterProvider() return nil } // Create metrics provider with the exporter provider := sdkmetric.NewMeterProvider( sdkmetric.WithReader(sdkmetric.NewPeriodicReader( exporter, sdkmetric.WithInterval(15*time.Second), )), ) // Set the global provider otel.SetMeterProvider(provider) meterProvider = provider log.Info("metrics setup: OTLP metrics provider initialized") return nil } // Shutdown gracefully shuts down the metrics provider. // This should be called during application shutdown to ensure all metrics // are properly flushed and exported. func Shutdown(ctx context.Context) error { if provider, ok := meterProvider.(*sdkmetric.MeterProvider); ok { return provider.Shutdown(ctx) } return nil }