feat(telemetry): add NewConsole — logz-backed OTel exporters for local dev (v1.1.0)
NewConsole bootstraps the OTel SDK with three logz-backed exporters: - Trace: WithSyncer, one log line per closed span (immediate, no batch) - Metric: PeriodicReader (60s), flushed on shutdown - OTel log: BatchProcessor, for third-party libs using OTel log API ConsoleConfig requires only ServiceName — no OTLP endpoint needed. Adds logz v1.0.1 as direct dependency; module tier bumped 1 → 2.
This commit is contained in:
36
CLAUDE.md
36
CLAUDE.md
@@ -8,20 +8,22 @@ Sets the three OTel global providers so that all micro-libs using the OTel globa
|
||||
|
||||
## Tier & Dependencies
|
||||
|
||||
**Tier 1** (no micro-lib dependencies; external OTel SDK only). Must never be imported by framework libraries.
|
||||
**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 micro-lib dependencies. No `launcher` dependency — telemetry has no Component lifecycle.
|
||||
No `launcher` dependency — telemetry has no Component lifecycle.
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
- **Tier 1 / 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.
|
||||
- **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.
|
||||
@@ -50,6 +52,23 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
**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
|
||||
@@ -73,14 +92,15 @@ lc.BeforeStop(func() error { return shutdown(ctx) })
|
||||
## 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` more than once per process. Each call overwrites the global providers.
|
||||
- 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
|
||||
|
||||
- The test file (`telemetry_test.go`) uses a `fakeCollector` that opens a TCP listener but speaks no gRPC protocol. This is sufficient to test that `New` succeeds and returns a callable shutdown function — the fake server accepts connections so the gRPC dial does not get connection-refused.
|
||||
- Tests that verify global provider replacement (`TestNew_SetsGlobalTracerProvider`, `TestNew_SetsGlobalMeterProvider`) must call `shutdown` in a `t.Cleanup` to restore state for subsequent tests. The short shutdown timeout (200ms) is intentional — the fake server cannot complete a gRPC flush, so errors from `shutdown(ctx)` are expected and ignored.
|
||||
- `newResource` is tested separately (`TestNewResource_Fields`, `TestNewResource_MergesWithDefault`) as a pure function with no I/O.
|
||||
- Do not test against a real Alloy or Tempo instance in unit tests. Use the fake collector pattern.
|
||||
- `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.
|
||||
|
||||
Reference in New Issue
Block a user