Single-call OTel SDK bootstrap setting all three global providers (traces → Tempo, metrics → Mimir, logs → Loki) over OTLP gRPC. What's included: - New(ctx, Config): bootstraps TracerProvider, MeterProvider, and LoggerProvider with OTLP gRPC exporters; sets OTel globals - W3C TraceContext + Baggage propagation set globally - Resource tagging: service.name, service.version, deployment.environment merged with SDK defaults - OTLPInsecure bool for development environments without TLS - Sequential rollback on partial initialization failure — no dangling exporters on error - Returns shutdown func(context.Context) error; caller defers in main or wires into launcher BeforeStop - Tier 5 module: must be imported only by application main packages; zero micro-lib dependencies Tested-via: todo-api POC integration Reviewed-against: docs/adr/
2.6 KiB
ADR-001: Tier 5 — Application Bootstrap Only
Status: Accepted Date: 2026-03-18
Context
OpenTelemetry is structured around a separation between the API (stable, zero-cost when no SDK is wired) and the SDK (the real implementation, with exporters, batch processors, and gRPC connections). Any package can import the OTel API and call otel.Tracer(...), otel.Meter(...), etc. at zero runtime cost — these calls are no-ops until an SDK TracerProvider is set as the global.
The question is where in the module tier hierarchy the SDK bootstrap belongs. The options are:
- Include telemetry bootstrap in each micro-lib that produces signals (e.g., httpserver starts its own SDK).
- Provide a standalone bootstrap module imported only by application
mainpackages.
Option 1 would cause multiple SDK initializations, competing global registrations, and make it impossible for the application to control the exporter endpoint or sampling strategy. It would also force all micro-libs to carry the heavy OTel SDK as a dependency even when the application does not use telemetry.
Decision
The telemetry module is Tier 5 — the same tier as application bootstrap entry points. It must only be imported by application main packages (or equivalent wiring code). It must never be imported by:
- Framework libraries (Tier 0–3)
- Transport modules (Tier 4)
- Other Tier 5 modules that are not themselves
main
Micro-libs use only the OTel API packages (go.opentelemetry.io/otel, go.opentelemetry.io/otel/metric, go.opentelemetry.io/otel/log) which default to no-op providers. When an application imports telemetry and calls telemetry.New(...), the three global providers are replaced with real SDK providers, and all micro-libs that use the global API automatically emit signals without any change to their code.
Consequences
- No micro-lib needs to import or configure
telemetry. The OTel no-op default means libraries compile and run correctly in unit tests without any collector present. - Applications that do not call
telemetry.New(...)produce no signals. This is correct — telemetry is opt-in at the application level. - The
telemetrymodule carries heavy SDK dependencies (OTLP gRPC exporters, batch processors). These do not appear in any library's dependency graph. - Code review must reject any PR that imports
telemetryfrom a non-mainpackage. This is enforced by convention, not by a build tool currently. - There is no
launcher.Componentwrapper for telemetry. The caller is responsible for deferring the shutdown function, which flushes all exporters before process exit.