Files
telemetry/doc.go
T
Rene Nochebuena d39a11e34c docs(telemetry): fix fictional launcher.Register/health.Register in doc examples (#2)
The package-doc examples showed a fictional launcher.Register / health.Register
API (and launcher.Append as a package func). Corrected to the real wiring:
lc.Append + web/health.NewHandler(...).ServeHTTP.
Also: launcher.BeforeStop (nonexistent) -> defer shutdown, matching the README.

Documentation-only. No code, API, or dependency changes; version stays v1.1.0
(the v1.1.0 tag is moved to include this fix). Verified by compiling the
corrected pattern against the real API.

Reviewed-on: #2
Co-authored-by: Rene Nochebuena Guerrero <rene@nochebuena.dev>
Co-committed-by: Rene Nochebuena Guerrero <rene@nochebuena.dev>
2026-08-07 22:18:05 -06:00

81 lines
2.8 KiB
Go

// 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)
//
// // Place the defer before lc.Run() so shutdown fires after the launcher
// // stops, before the process exits.
// }
//
// # 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