Files
mats 6be6aa891a otellog: slog bridge that records errors on the active span
Error/Info helpers emit a structured log record AND touch the active span
(status + stack trace), so failures show up as red markers in Jaeger/Tempo
with correlated log lines. No-op safe when no span is active. First consumer:
go-cart-actor cmd/order.
2026-08-24 16:11:44 +02:00

63 lines
2.5 KiB
Go

// Package otellog provides logging helpers that bridge slog with OpenTelemetry
// spans. Every Error/Info call both emits a structured log record AND touches
// the active span — so errors appear as red markers in Jaeger/Tempo AND as
// correlated log lines in your log backend (Loki, Elasticsearch, etc.).
//
// Usage:
//
// otellog.Error(ctx, logger, "checkout failed", err, "orderId", id.String())
//
// The helpers are safe when no span is active (the span is a no-op in that
// case) — callers don't need to guard the call.
package otellog
import (
"context"
"log/slog"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
)
// Error records an error on the active span (with stack trace) and emits a
// structured slog.ErrorContext log. The span status is set to Error.
// Any extra key/value pairs are appended to the log record.
func Error(ctx context.Context, logger *slog.Logger, msg string, err error, args ...any) {
if span := trace.SpanFromContext(ctx); span != nil {
span.RecordError(err, trace.WithStackTrace(true))
span.SetStatus(codes.Error, msg)
}
all := make([]any, 0, len(args)+2)
all = append(all, "error", err)
all = append(all, args...)
logger.ErrorContext(ctx, msg, all...)
}
// Info emits a structured slog.InfoContext log. When a span is active the
// trace/span IDs are automatically injected by the OTel slog handler (if
// configured), correlating the log line with the trace.
func Info(ctx context.Context, logger *slog.Logger, msg string, args ...any) {
logger.InfoContext(ctx, msg, args...)
}
// Warn emits a structured slog.WarnContext log. Like Info, span correlation
// is handled by the OTel slog handler when configured.
func Warn(ctx context.Context, logger *slog.Logger, msg string, args ...any) {
logger.WarnContext(ctx, msg, args...)
}
// SpanFromContext returns the current span — useful when callers need to add
// attributes or events directly. Returns a valid (no-op) span when no span is
// active, so the caller never needs a nil check.
func SpanFromContext(ctx context.Context) trace.Span {
return trace.SpanFromContext(ctx)
}
// StartSpan is a convenience wrapper around otel.Tracer(name).Start(ctx, name)
// that returns the ctx and span. Panics if the tracer provider is not
// configured — but in practice the global no-op provider handles that.
func StartSpan(ctx context.Context, tracerName, spanName string, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
return otel.Tracer(tracerName).Start(ctx, spanName, opts...)
}