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/
34 lines
2.6 KiB
Markdown
34 lines
2.6 KiB
Markdown
# 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:
|
||
|
||
1. Include telemetry bootstrap in each micro-lib that produces signals (e.g., httpserver starts its own SDK).
|
||
2. Provide a standalone bootstrap module imported only by application `main` packages.
|
||
|
||
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 `telemetry` module 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 `telemetry` from a non-`main` package. This is enforced by convention, not by a build tool currently.
|
||
- There is no `launcher.Component` wrapper for telemetry. The caller is responsible for deferring the shutdown function, which flushes all exporters before process exit.
|