# telemetry Bootstraps the full OpenTelemetry SDK (traces, metrics, logs) with OTLP gRPC exporters targeting Grafana Alloy. ## Purpose Sets the three OTel global providers so that all micro-libs using the OTel global API auto-instrument without any code changes. Returns a shutdown function that flushes all exporters on process exit. This module is the single place in an application where the OTel SDK is wired up. ## Tier & Dependencies **Tier 2** — depends on Tier 1 `logz` (required by `NewConsole`). Must never be imported by framework libraries. Depends on: - `code.nochebuena.dev/go/logz` (Tier 1) — used by `NewConsole` exporters - `go.opentelemetry.io/otel` and sub-packages — API and SDK - `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc` - `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc` - `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc` - `go.opentelemetry.io/otel/sdk/trace`, `.../metric`, `.../log` No `launcher` dependency — telemetry has no Component lifecycle. ## Key Design Decisions - **Tier 2 / app-only** (ADR-001): Libraries use only the OTel API (no-op default). This module activates the real SDK. Importing it from a library is a mistake. - **Three-signal OTLP bootstrap** (ADR-002): `New(ctx, cfg)` sets up traces → Tempo, metrics → Mimir, logs → Loki, all over a single OTLP gRPC endpoint. W3C TraceContext + Baggage propagation is set globally. - **Global provider strategy** (ADR-003): Libraries call `otel.Tracer(...)` / `otel.Meter(...)` / `global.Logger(...)`. After `telemetry.New`, those calls route to the real SDK with no library changes required. - **No `launcher.Component`**: Telemetry is not a lifecycle component. The caller defers the returned shutdown function directly in `main`. This keeps the module dependency graph minimal and the interface simple. - **Sequential error rollback**: If any exporter fails to initialize, all previously created providers are shut down before the error is returned. The process never runs with a partial telemetry state. ## Patterns **Standard application usage:** ```go 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) // Rest of application wiring... } ``` **Local development — console mode (no collector required):** ```go func main() { ctx := context.Background() logger := logz.New(logz.Options{}) shutdown, err := telemetry.NewConsole(ctx, logger, telemetry.ConsoleConfig{ ServiceName: "order-service", }) if err != nil { log.Fatalf("telemetry: %v", err) } defer shutdown(ctx) // Traces, metrics, and OTel log records appear as logz log lines. } ``` **With launcher (wire shutdown into lifecycle):** ```go shutdown, err := telemetry.New(ctx, cfg) if err != nil { return err } lc.BeforeStop(func() error { return shutdown(ctx) }) ``` **Config env vars:** | Variable | Required | Default | Description | |---|---|---|---| | `OTEL_SERVICE_NAME` | yes | — | Service name in all signals | | `OTEL_SERVICE_VERSION` | no | `unknown` | Deployed version | | `OTEL_ENVIRONMENT` | no | `development` | Deployment environment | | `OTEL_EXPORTER_OTLP_ENDPOINT` | yes | — | OTLP gRPC collector address (e.g. `alloy:4317`) | | `OTEL_EXPORTER_OTLP_INSECURE` | no | `false` | Disable TLS (set `true` for local dev) | ## What to Avoid - Do not import this module from any non-`main` package. Libraries must use only OTel API packages. - Do not call `telemetry.New` or `telemetry.NewConsole` more than once per process. Each call overwrites the global providers. - Do not omit the `defer shutdown(ctx)`. Without it, buffered spans and metrics are lost on exit. - Do not use a zero-value `Config`. Both `ServiceName` and `OTLPEndpoint` are required; `New` will return an error if the OTLP connection cannot be established. - Do not wrap this in a `launcher.Component`. The shutdown function pattern is simpler and avoids adding a `launcher` dependency to this module. - Do not wire the OTel slog bridge alongside `NewConsole`. The bridge routes logz/slog output through the OTel log API, which `logLogExporter` forwards back to logz — creating a feedback loop. ## Testing Notes - `telemetry_test.go` — uses a `fakeCollector` TCP listener to avoid connection-refused; tests `New` and the global provider assignments. Shutdown timeout 200ms is intentional since the fake server cannot complete a gRPC flush. - `console_test.go` — uses a `stubLogger` that captures log calls. `TestNewConsole_ExportsSpan` uses `WithSyncer` behavior: span export is synchronous, so the log is recorded immediately on `span.End()`. `TestNewConsole_ExportsMetric` triggers export via shutdown flush (PeriodicReader flushes on Shutdown). - `newResource` is tested separately as a pure function with no I/O. - Do not test against a real Alloy or Tempo instance in unit tests.