// Package telemetry bootstraps the OpenTelemetry SDK for Einherjar applications. // // # Overview // // There are two bootstrap functions — one for production (OTLP over gRPC) and one // for local development (structured log output). Both set the three OTel global // providers so that all starters using otel.Tracer / otel.Meter / global.Logger // auto-instrument without any code changes. // // This package is app-only: import it only from main packages. Never import it // from a starter or library — starters use only the OTel API, which is a zero-cost // no-op until a real SDK is wired up here. // // # Production: OTLP over gRPC // // [New] connects to a Grafana Alloy (or any OTLP-compatible) collector and // exports traces → Tempo, metrics → Mimir, and logs → Loki. // // func main() { // ctx := context.Background() // // shutdown, err := telemetry.New(ctx, telemetry.Config{ // ServiceName: "order-service", // ServiceVersion: "1.4.2", // Environment: "production", // OTLPEndpoint: "alloy:4317", // OTLPInsecure: false, // }) // if err != nil { // log.Fatalf("telemetry: %v", err) // } // defer shutdown(ctx) // // // Wire into the launcher so shutdown runs before process exit: // // launcher.BeforeStop(func() error { return shutdown(ctx) }) // } // // # Local Development: console mode // // [NewConsole] routes all three signals through a [logging.Logger] as structured // log lines. No collector is required — spans, metrics, and OTel log records appear // inline with your application logs. // // func main() { // ctx := context.Background() // logger := logz.New(logz.Config{}) // // shutdown, err := telemetry.NewConsole(ctx, logger, telemetry.ConsoleConfig{ // ServiceName: "order-service", // }) // if err != nil { // log.Fatalf("telemetry: %v", err) // } // defer shutdown(ctx) // } // // # Avoiding the slog feedback loop // // logz is backed by slog. The OTel ecosystem provides a slog bridge // (go.opentelemetry.io/contrib/bridges/otelslog) that forwards slog records into // the OTel log API. Do NOT use that bridge together with [NewConsole]. // // The loop is: // // slog.Info("msg") // → OTel log API (via slog bridge) // → logLogExporter.Export() // → logger.Info("otel: log", ...) ← this is slog again // → OTel log API (via slog bridge) // → ... ∞ // // The slog bridge is safe with [New] because the OTLP exporter sends records over // the network — it never calls back into slog. The loop only occurs with [NewConsole] // because its log exporter writes back to the same logger that feeds it. // // Rule of thumb: // - [New] + slog bridge: safe ✓ // - [NewConsole] + slog bridge: feedback loop ✗ // - [NewConsole] without slog bridge: safe ✓ package telemetry