feat(telemetry): initial implementation — OpenTelemetry traces, metrics, logs (v1.0.0)
Introduces code.nochebuena.dev/einherjar/telemetry — the observability bootstrap starter for the Einherjar framework. Absorbs the telemetry package from micro-lib, migrating from OpenCensus to OpenTelemetry SDK v1.42. Bootstrap functions (not lifecycle.Component — telemetry must be initialized before the launcher starts, and its shutdown must run after all components stop): - New(ctx, cfg) (func(context.Context) error, error) — production mode; exports traces, metrics, and logs via OTLP over gRPC to the configured endpoint; returns a shutdown function to be deferred in main() - NewConsole(ctx, logger, cfg) (func(context.Context) error, error) — development mode; writes structured telemetry to the provided logging.Logger; no network dependency; suitable for local development and CI Config (EINHERJAR_OTEL_* env vars): ServiceName(required), ServiceVersion(unknown), Environment(development), OTLPEndpoint(required for New), OTLPInsecure(false) ConsoleConfig (EINHERJAR_OTEL_* env vars): ServiceName(required), ServiceVersion(unknown), Environment(development) - identifiable.go: package-level Module variable (observability.Identifiable) for version identification — telemetry bootstraps before the launcher; not registered as a lifecycle component
This commit is contained in:
80
doc.go
Normal file
80
doc.go
Normal file
@@ -0,0 +1,80 @@
|
||||
// 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)
|
||||
//
|
||||
// // Wire into the launcher so shutdown runs before process exit:
|
||||
// // launcher.BeforeStop(func() error { return shutdown(ctx) })
|
||||
// }
|
||||
//
|
||||
// # 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
|
||||
Reference in New Issue
Block a user