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.
81 lines
2.8 KiB
Go
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
|