Documentation
¶
Overview ¶
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 ✓
Index ¶
Constants ¶
This section is empty.
Variables ¶
var Module observability.Identifiable = &moduleID{}
Module identifies this package to observability systems. telemetry bootstraps before the launcher and is not registered as a lifecycle component. Register Module manually with any version registry if needed.
Functions ¶
func New ¶
New bootstraps the full OTel SDK:
- TracerProvider → OTLP gRPC → Grafana Alloy → Tempo
- MeterProvider → OTLP gRPC → Grafana Alloy → Mimir
- LoggerProvider → OTLP gRPC → Grafana Alloy → Loki
Sets the three OTel globals so all starters using the global API auto-instrument without importing this module.
The returned shutdown function flushes all exporters and must be called before process exit (defer it in main or wire it into the launcher). Returns (shutdown, nil) on success, (nil, err) on failure.
func NewConsole ¶
func NewConsole(ctx context.Context, logger logging.Logger, cfg ConsoleConfig) (func(context.Context) error, error)
NewConsole bootstraps the OTel SDK with logger-backed exporters for local development. All signals (traces, metrics, OTel log records) are emitted as structured log lines instead of being sent to a collector. Drop-in alternative to New.
Warning: do not use the OTel slog bridge (otelslog) together with NewConsole. The bridge routes slog records into the OTel log API; logLogExporter then writes them back to the same logger — creating an infinite feedback loop. See package documentation for the full explanation and safe usage patterns.
Types ¶
type Config ¶
type Config struct {
// ServiceName identifies the service in traces, metrics, and logs.
ServiceName string `env:"EINHERJAR_OTEL_SERVICE_NAME,required"`
// ServiceVersion is the deployed version (e.g. "1.4.2").
ServiceVersion string `env:"EINHERJAR_OTEL_SERVICE_VERSION" envDefault:"unknown"`
// Environment is the deployment environment (e.g. "production", "staging").
Environment string `env:"EINHERJAR_OTEL_ENVIRONMENT" envDefault:"development"`
// OTLPEndpoint is the OTLP gRPC collector address (e.g. "alloy:4317").
OTLPEndpoint string `env:"EINHERJAR_OTEL_EXPORTER_ENDPOINT,required"`
// OTLPInsecure disables TLS for the OTLP connection. Set true in development.
OTLPInsecure bool `env:"EINHERJAR_OTEL_EXPORTER_INSECURE" envDefault:"false"`
}
Config holds OTel bootstrap configuration.
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns a Config with optional fields set to production-safe defaults. Callers must supply ServiceName and OTLPEndpoint.
type ConsoleConfig ¶
type ConsoleConfig struct {
ServiceName string `env:"EINHERJAR_OTEL_SERVICE_NAME,required"`
ServiceVersion string `env:"EINHERJAR_OTEL_SERVICE_VERSION" envDefault:"unknown"`
Environment string `env:"EINHERJAR_OTEL_ENVIRONMENT" envDefault:"development"`
}
ConsoleConfig holds the minimum OTel configuration needed for console/dev mode. Only service identity fields are required — no OTLP endpoint.
func DefaultConsoleConfig ¶
func DefaultConsoleConfig() ConsoleConfig
DefaultConsoleConfig returns a ConsoleConfig with optional fields set to defaults. Callers must supply ServiceName.