Documentation
¶
Overview ¶
Package slog sinks all three OpenTelemetry signals — Traces, Metrics, and Logs — into a log/slog logger, so every span, metric, and log line from any Scope module lands in one structured stream correlated by trace_id / span_id.
Three exporters, one per signal:
- SpanExporter (sdktrace.SpanExporter) — install via WithSyncer / WithBatcher
- MetricExporter (sdkmetric.Exporter) — install via a PeriodicReader
- LogExporter (sdklog.Exporter) — install via a LoggerProvider processor
This is for local development and debugging, where forwarding to a full backend (Jaeger, Tempo, Datadog, ...) would be overkill. Routing logs through OTel (rather than writing slog directly) is deliberate: it makes logs as backend-swappable as traces/metrics — a production build swaps each exporter to OTLP with zero business-code change.
Usage ¶
import (
stdslog "log/slog"
"go.opentelemetry.io/otel"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"github.com/Tangerg/scope/otel/slog"
)
tp := sdktrace.NewTracerProvider(
sdktrace.WithSyncer(slog.NewSpanExporter(stdslog.Default())),
)
otel.SetTracerProvider(tp)
defer tp.Shutdown(context.Background())
The log path goes through the contrib otelslog bridge to a LoggerProvider configured with LogExporter.
Note: this package is named `slog` to match the otel/<backend> convention. Callers that also import the standard library's `log/slog` must alias one of them (commonly `stdslog "log/slog"`) to avoid the name collision.
For production use, prefer OTLP exporters to a real backend; these exporters are intended for local visibility, not long-term storage.
Index ¶
- type LogExporter
- type MetricExporter
- func (m *MetricExporter) Aggregation(k sdkmetric.InstrumentKind) sdkmetric.Aggregation
- func (m *MetricExporter) Export(ctx context.Context, rm *metricdata.ResourceMetrics) error
- func (m *MetricExporter) ForceFlush(ctx context.Context) error
- func (m *MetricExporter) Shutdown(ctx context.Context) error
- func (m *MetricExporter) Temporality(k sdkmetric.InstrumentKind) metricdata.Temporality
- type SpanExporter
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type LogExporter ¶
type LogExporter struct {
// contains filtered or unexported fields
}
LogExporter writes OpenTelemetry log records to a log/slog logger — the Logs leg of the dev observability triad, a sibling of SpanExporter and MetricExporter so all three OTel signals share one slog stream keyed by trace_id.
Application code keeps calling slog (via the contrib otelslog bridge that feeds a LoggerProvider); installing this exporter on that provider is what makes logs as backend-swappable as traces/metrics — a production build swaps it for an OTLP log exporter (→ Datadog / Cloud Logging / ...) with zero business-code change. That swappability is the whole reason logs go through OTel rather than straight to slog.
Install it on a LoggerProvider via a processor:
lp := sdklog.NewLoggerProvider(
sdklog.WithProcessor(sdklog.NewSimpleProcessor(slog.NewLogExporter(logger))))
Each OTel log record becomes one slog record: the record body is the message, the severity maps to the slog level, and trace_id / span_id come from the record's own trace context (the SDK fills them from the emitting span — native correlation, no manual stamping).
func NewLogExporter ¶
func NewLogExporter(logger *stdslog.Logger) *LogExporter
NewLogExporter writes records through log/slog so telemetry is readable in development without running a collector. The exporter adds no delivery guarantees of its own and is intended for local diagnostics.
func (*LogExporter) Export ¶
Export writes one slog record per OTel log record. It returns only context cancellation/deadline errors and sdklog.ErrExporterShutdown when closed.
func (*LogExporter) ForceFlush ¶
func (l *LogExporter) ForceFlush(ctx context.Context) error
type MetricExporter ¶
type MetricExporter struct {
// contains filtered or unexported fields
}
MetricExporter writes OpenTelemetry metric data to a log/slog logger — the Metrics leg of the dev observability triad, a sibling of SpanExporter so all three signals share one slog stream.
Install it on a MeterProvider via a PeriodicReader:
reader := sdkmetric.NewPeriodicReader(slog.NewMetricExporter(logger)) mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) otel.SetMeterProvider(mp)
Each metric becomes one slog record carrying the instrument name, unit, scope, and a compact rendering of its data points. Like SpanExporter this is for local visibility; production should use an OTLP metric exporter.
func NewMetricExporter ¶
func NewMetricExporter(logger *stdslog.Logger) *MetricExporter
NewMetricExporter writes metrics through log/slog for the same reason as NewLogExporter — inspecting instrumentation locally without a collector — and carries the same caveat about production volume.
func (*MetricExporter) Aggregation ¶
func (m *MetricExporter) Aggregation(k sdkmetric.InstrumentKind) sdkmetric.Aggregation
func (*MetricExporter) Export ¶
func (m *MetricExporter) Export(ctx context.Context, rm *metricdata.ResourceMetrics) error
Export writes one slog record per metric. It reports cancellation, nil input, and the SDK's shutdown state; slog handler failures are not exposed by log/slog and therefore cannot become collection errors.
func (*MetricExporter) ForceFlush ¶
func (m *MetricExporter) ForceFlush(ctx context.Context) error
func (*MetricExporter) Temporality ¶
func (m *MetricExporter) Temporality(k sdkmetric.InstrumentKind) metricdata.Temporality
Temporality / Aggregation defer to the SDK defaults — this is a passive dev sink with no opinion on accumulation semantics.
type SpanExporter ¶
type SpanExporter struct {
// contains filtered or unexported fields
}
SpanExporter writes finished OpenTelemetry spans to a log/slog logger — the Traces leg of the dev observability triad, a sibling of MetricExporter and LogExporter so all three OTel signals share one slog stream keyed by trace_id.
It implements sdktrace.SpanExporter and is intended to be installed on a TracerProvider via sdktrace.WithSyncer (for dev/debug, synchronous output) or sdktrace.WithBatcher (for production-ish batched output).
Each span becomes a single slog record. The record message is "span" for OK/Unset status and "span (error): <description>" for Error status, with the log level promoted to Error accordingly.
The following attributes are always included:
- trace_id (span.SpanContext().TraceID())
- span_id (span.SpanContext().SpanID())
- name (span.Name())
- duration (EndTime - StartTime)
- parent_span_id (only if the span has a parent)
All span attributes and event names are attached as additional slog attributes, preserving their OTel key names (e.g. "gen_ai.provider.name").
func NewSpanExporter ¶
func NewSpanExporter(logger *stdslog.Logger) *SpanExporter
NewSpanExporter writes spans through log/slog for the same reason as NewLogExporter. Each finished span becomes an independent record, so trace structure remains encoded in its identifiers rather than rendered as a tree.
func (*SpanExporter) ExportSpans ¶
func (s *SpanExporter) ExportSpans(ctx context.Context, spans []sdktrace.ReadOnlySpan) error
ExportSpans writes each provided span as a single slog record. It returns context cancellation/deadline errors; slog handler failures are not exposed by log/slog. After Shutdown it performs no work.