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.
This commit is contained in:
1 parent
be9ecd538b
commit
6be6aa891a
1 file changed
+62
@@ -0,0 +1,62 @@
|
||||
// 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...)
|
||||
}
|
||||
Reference in new issue
Block a user