telemetry

package module
v1.7.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 19, 2026 License: AGPL-3.0 Imports: 20 Imported by: 0

README

einherjar/telemetry

version license go

Huginn and Muninn fly each day over the world. They see everything. They report back.

code.nochebuena.dev/einherjar/telemetry is the OpenTelemetry bootstrap component of the Einherjar framework. It initializes traces, metrics, and structured logs via OTLP over gRPC — vendor-neutral, compatible with Grafana, Jaeger, Tempo, Datadog, and Honeycomb. A console mode is available for local development without a collector.

Telemetry is not a lifecycle.Component. It must be initialized before the launcher and shut down after all components stop — the returned shutdown function handles this cleanly with a defer.


Usage

Production (OTLP/gRPC)
import (
    "code.nochebuena.dev/einherjar/telemetry"
    "code.nochebuena.dev/einherjar/core/launcher"
    "code.nochebuena.dev/einherjar/core/logz"
)

ctx := context.Background()
logger := logz.New(logz.Config{JSON: true})

// Initialize telemetry BEFORE the launcher.
// Defer the shutdown BEFORE launcher.Run() so it fires after the launcher stops.
shutdown, err := telemetry.New(ctx, telemetry.DefaultConfig())
if err != nil {
    logger.Error("telemetry init failed", err)
    os.Exit(1)
}
defer shutdown(ctx)

lc := launcher.New(logger)
lc.Append(db, cache, srv)
lc.Run()
Development (console / structured log output)

No collector required. Spans and metrics are emitted to the configured logging.Logger.

shutdown, err := telemetry.NewConsole(ctx, logger, telemetry.DefaultConsoleConfig())
if err != nil {
    logger.Error("telemetry init failed", err)
    os.Exit(1)
}
defer shutdown(ctx)

Environment variables

Production (telemetry.New)
Variable Required Default Description
EINHERJAR_OTEL_SERVICE_NAME Yes Service name reported to the collector
EINHERJAR_OTEL_EXPORTER_ENDPOINT Yes OTLP gRPC endpoint (e.g. otel-collector:4317)
EINHERJAR_OTEL_SERVICE_VERSION No unknown Service version tag
EINHERJAR_OTEL_ENVIRONMENT No development Deployment environment tag
EINHERJAR_OTEL_EXPORTER_INSECURE No false Disable TLS for the exporter (dev/local)
Development (telemetry.NewConsole)
Variable Required Default Description
EINHERJAR_OTEL_SERVICE_NAME Yes Service name
EINHERJAR_OTEL_SERVICE_VERSION No unknown Service version tag
EINHERJAR_OTEL_ENVIRONMENT No development Deployment environment tag

Dependency graph

contracts  (zero dependencies)
    ↑
  core
    ↑
telemetry  (contracts, core, otel SDK + OTLP exporters)
    ↑
  your app (initialized before launcher.Run)

Verification

cd telemetry/
go build ./...
go vet ./...
go test ./...
gofmt -l .

Odin gave an eye for wisdom. Observability is the eye that watches the living system.

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:

Index

Constants

This section is empty.

Variables

View Source
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

func New(ctx context.Context, cfg Config) (func(context.Context) error, error)

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL