Documentation
¶
Overview ¶
Package logger is the Go binding for dagstack/logger-spec — OTel-compatible structured logging with named loggers, automatic context propagation, scoped sink overrides, and a dagstack JSON-lines wire format.
Spec: https://github.com/dagstack/logger-spec (ADR-0001 v1.2). Reference implementation: https://github.com/dagstack/logger-python.
Status: v0.2.0 (Phase 1). Phase 1 sinks (Console, File, InMemory) are implemented, plus the Phase 1 redaction-config public API. OTLPSink, processor chain, and self-metrics arrive in Phase 2.
Typical usage:
logger.Configure(
logger.WithRootLevel("INFO"),
logger.WithSinks(logger.NewConsoleSink(logger.ConsoleAuto, os.Stderr, 1)),
logger.WithResourceAttributes(map[string]any{
"service.name": "pilot-app",
}),
)
log := logger.Get("dagstack.rag.retriever", "1.4.2")
log.Info("query received", logger.Attrs{"user.id": 42})
Context propagation reads OTel trace state from the supplied context.Context. Use the *Ctx variants of the severity methods to enable trace_id/span_id auto-injection:
log.InfoCtx(ctx, "in span", nil)
Scoped sink overrides per spec §6:
mem := logger.NewInMemorySink(100, 1)
scoped := log.WithSinks(mem)
scoped.Info("captured only here", nil)
For lexically bounded scope, use ScopeSinks (callback-style; the spec's "Go ctx + defer" idiom is also exposed via WithSinks + manual restoration):
err := log.ScopeSinks(ctx, []logger.Sink{mem}, func(ctx context.Context) error {
return runAgentPipeline(ctx)
})
Phase 1 does not support runtime watch — OnReconfigure registers a Subscription with Active=false, InactiveReason is set to a diagnostic string, and the callback never fires.
Index ¶
- Constants
- Variables
- func ActiveTraceContext(ctx context.Context) (traceID, spanID []byte, traceFlags uint8)
- func BuildEffectiveSuffixes(cfg RedactionConfig) []string
- func CanonicalJSONMarshal(v any) ([]byte, error)
- func CanonicalJSONMarshalString(v any) (string, error)
- func Configure(opts ...ConfigureOption)
- func DecodeSpanID(hexStr string) ([]byte, error)
- func DecodeTraceID(hexStr string) ([]byte, error)
- func EncodeSpanID(spanID []byte) (string, error)
- func EncodeTraceID(traceID []byte) (string, error)
- func IsCanonicalSeverityText(text string) bool
- func IsSecretKey(key string, suffixes []string) bool
- func IsValidSeverityNumber(severityNumber int) bool
- func Reset()
- func SeverityTextFor(severityNumber int) (string, error)
- func ToDagstackJSONL(record *LogRecord) (string, error)
- func ToDagstackJSONLDict(record *LogRecord) (map[string]any, error)
- func ValidateRedactionConfig(cfg RedactionConfig) error
- type Attrs
- type ConfigureOption
- func WithAutoInjectTraceContext(enabled bool) ConfigureOption
- func WithPerLoggerLevels(levels map[string]any) ConfigureOption
- func WithRedactionConfig(cfg RedactionConfig) ConfigureOption
- func WithResourceAttributes(attrs Attrs) ConfigureOption
- func WithRootLevel(level any) ConfigureOption
- func WithSinks(sinks ...Sink) ConfigureOption
- type ConsoleMode
- type ConsoleSink
- type FileSink
- type FlushFailure
- type FlushResult
- type InMemorySink
- func (s *InMemorySink) Capacity() int
- func (s *InMemorySink) Clear()
- func (s *InMemorySink) Close() error
- func (s *InMemorySink) Emit(record *LogRecord)
- func (s *InMemorySink) Flush(_ float64) error
- func (s *InMemorySink) ID() string
- func (s *InMemorySink) Records() []*LogRecord
- func (s *InMemorySink) SupportsSeverity(severityNumber int) bool
- type InstrumentationScope
- type LogRecord
- type Logger
- func (l *Logger) AppendSinks(extra ...Sink) *Logger
- func (l *Logger) Child(attrs Attrs) *Logger
- func (l *Logger) Close() error
- func (l *Logger) Debug(body any, attrs Attrs)
- func (l *Logger) DebugCtx(ctx context.Context, body any, attrs Attrs)
- func (l *Logger) EffectiveMinSeverity() int
- func (l *Logger) EffectiveResource() *Resource
- func (l *Logger) EffectiveSecretSuffixes() []string
- func (l *Logger) EffectiveSinks() []Sink
- func (l *Logger) Error(body any, attrs Attrs)
- func (l *Logger) ErrorCtx(ctx context.Context, body any, attrs Attrs)
- func (l *Logger) Exception(err error, body any, attrs Attrs)
- func (l *Logger) ExceptionCtx(ctx context.Context, err error, body any, attrs Attrs)
- func (l *Logger) Fatal(body any, attrs Attrs)
- func (l *Logger) FatalCtx(ctx context.Context, body any, attrs Attrs)
- func (l *Logger) Flush(timeoutSeconds float64) (*FlushResult, error)
- func (l *Logger) Info(body any, attrs Attrs)
- func (l *Logger) InfoCtx(ctx context.Context, body any, attrs Attrs)
- func (l *Logger) Log(severityNumber int, body any, attrs Attrs)
- func (l *Logger) LogCtx(ctx context.Context, severityNumber int, body any, attrs Attrs)
- func (l *Logger) Name() string
- func (l *Logger) OnReconfigure(_ func()) *Subscription
- func (l *Logger) ScopeSinks(ctx context.Context, sinks []Sink, fn func(context.Context) error) error
- func (l *Logger) SetMinSeverity(severityNumber int)
- func (l *Logger) SetRedactionSuffixes(suffixes []string)
- func (l *Logger) SetResource(r *Resource)
- func (l *Logger) SetSinks(sinks []Sink)
- func (l *Logger) Trace(body any, attrs Attrs)
- func (l *Logger) TraceCtx(ctx context.Context, body any, attrs Attrs)
- func (l *Logger) Version() string
- func (l *Logger) Warn(body any, attrs Attrs)
- func (l *Logger) WarnCtx(ctx context.Context, body any, attrs Attrs)
- func (l *Logger) WithSinks(sinks ...Sink) *Logger
- func (l *Logger) WithoutSinks() *Logger
- type RedactionConfig
- type Resource
- type Severity
- type Sink
- type Subscription
Constants ¶
const ( SeverityTextTrace = "TRACE" SeverityTextDebug = "DEBUG" SeverityTextInfo = "INFO" SeverityTextWarn = "WARN" SeverityTextError = "ERROR" SeverityTextFatal = "FATAL" )
Canonical severity_text strings — exactly the 6 OTel-recommended values per spec §2. Backends filter by exact match.
const RedactedPlaceholder = "***"
RedactedPlaceholder is the string used to replace secret-suffix attribute values per spec ADR-0001 §10.1 — a literal "***".
Variables ¶
var CanonicalSeverityTexts = [6]string{ SeverityTextTrace, SeverityTextDebug, SeverityTextInfo, SeverityTextWarn, SeverityTextError, SeverityTextFatal, }
CanonicalSeverityTexts is the ordered set of the 6 canonical severity_text strings. Bindings must not emit any other value for severity_text.
var DefaultBaggageKeys = []string{"tenant.id", "request.id", "user.id"}
DefaultBaggageKeys is the allow-list of W3C Baggage entries auto-injected into LogRecord attributes per spec ADR-0001 §3.4. Other baggage entries are skipped to avoid leaking arbitrary cross-service context into logs.
Phase 1 delivers the trace-context portion only; baggage extraction is gated on availability of the OTel baggage API and can be enabled in a future minor release without breaking the surface.
var DefaultSecretSuffixes = []string{
"_key",
"_secret",
"_token",
"_password",
"_passphrase",
"_credentials",
}
DefaultSecretSuffixes is the canonical set of suffix patterns matched case-insensitively against attribute keys. Per spec §10.1 / §10.4 v1.1 this is an opinionated 6-element subset of `config-spec/_meta/secret_patterns.yaml`. The list is fixed at v1.1 to preserve API stability; richer matchers ship via the Phase 2 processor pipeline (§10.3).
A key whose lowercased form ends with any of these suffixes is treated as secret and its value is replaced with RedactedPlaceholder before serialization.
Functions ¶
func ActiveTraceContext ¶
ActiveTraceContext extracts the active OTel SpanContext from ctx and returns its (TraceID, SpanID, TraceFlags) triple ready for LogRecord fields. When no valid span context is present, returns (nil, nil, 0).
Per spec §3.4 — the binding MUST use the OTel Context API as the source of trace state, not its own context implementation.
func BuildEffectiveSuffixes ¶ added in v0.2.0
func BuildEffectiveSuffixes(cfg RedactionConfig) []string
BuildEffectiveSuffixes produces the post-Configure suffix list applied during emit. When ReplaceDefaults=false, the result is the union of DefaultSecretSuffixes and ExtraSuffixes (deduplicated, lowercased). When ReplaceDefaults=true, only ExtraSuffixes are returned (also deduplicated and lowercased). Returns a non-nil empty slice when the resulting set is empty (replace-all-off mode); callers MUST treat that as "no suffix-based masking" — never fall back to DefaultSecretSuffixes silently, otherwise replace mode loses its intent.
BuildEffectiveSuffixes does NOT validate cfg.ExtraSuffixes — callers passing untrusted input MUST run ValidateRedactionConfig first, or use WithRedactionConfig which validates at option-construction time.
func CanonicalJSONMarshal ¶
CanonicalJSONMarshal serializes a value into a canonical JSON byte slice per RFC 8785 subset (config-spec §9.1.1).
Rules:
- Sorted object keys (lexicographic UTF-16 code-unit order per RFC 8785 §3.2.3 — matches the cross-binding canonical sort applied by dagstack-logger Python and @dagstack/logger TypeScript).
- No whitespace except inside strings.
- Integers without a decimal point ("1"); floats use shortest round-trip.
- "-0.0" → "0.0" (RFC 8785 §3.2.2.3).
- NaN / ±Infinity → error.
- Non-string map keys → error.
- UTF-8 strings pass through unescaped (HTML-safe escaping disabled).
The implementation mirrors logger-python's canonical_json.canonical_json_dumps to guarantee byte-identical output across bindings.
func CanonicalJSONMarshalString ¶
CanonicalJSONMarshalString is a convenience wrapper that returns the result as a string.
func Configure ¶
func Configure(opts ...ConfigureOption)
Configure applies the bootstrap options to the global logger state.
The root logger is updated atomically: min-severity, sinks, per-logger level overrides, and the Resource attribute set. Unspecified groups preserve their previous values when called more than once (so a partial reconfigure stays safe).
Invalid severity strings are rejected at option-construction time — WithRootLevel and WithPerLoggerLevels panic if the string does not resolve to a canonical level (TRACE / DEBUG / INFO / WARN / ERROR / FATAL or a 1–24 integer). Callers typically resolve options once at startup, where a panic is acceptable (recover-on-startup is a tested pattern). Configure itself does not validate further once the options have been built.
func DecodeSpanID ¶
DecodeSpanID decodes a 16-character hex string into an 8-byte span_id. Returns nil when hexStr is empty.
func DecodeTraceID ¶
DecodeTraceID decodes a 32-character hex string into a 16-byte trace_id. Returns nil when hexStr is empty.
func EncodeSpanID ¶
EncodeSpanID encodes an 8-byte span_id as a 16-character lowercase hex string. Returns an empty string when spanID is nil.
func EncodeTraceID ¶
EncodeTraceID encodes a 16-byte trace_id as a 32-character lowercase hex string. Per spec ADR-0001 §1 — wire-format encoding for dagstack JSON-lines and OTel JSON. Returns an empty string when traceID is nil.
func IsCanonicalSeverityText ¶
IsCanonicalSeverityText reports whether text is one of the 6 canonical OTel-recommended strings.
func IsSecretKey ¶
IsSecretKey reports whether key matches any of the suffix patterns, case-insensitively. The suffixes parameter allows callers to override the default set; pass nil to use DefaultSecretSuffixes.
func IsValidSeverityNumber ¶
IsValidSeverityNumber reports whether severityNumber is in the valid [1, 24] range.
func Reset ¶ added in v0.2.0
func Reset()
Reset clears the global Logger registry — restores root defaults.
For test isolation: call between tests that mutate logger state via SetSinks / SetMinSeverity / SetResource or via Configure. After Reset, a subsequent Get(name) creates a fresh node with no inherited overrides.
SAFETY: this is a coarse instrument — it invalidates ALL logger handles still held elsewhere. Production code MUST NOT call this; it is reserved for test fixtures and the binding's own teardown.
func SeverityTextFor ¶
SeverityTextFor maps a severity_number in [1, 24] to its canonical severity_text string. Returns an error if severity_number is out of range.
func ToDagstackJSONL ¶
ToDagstackJSONL serializes a LogRecord as a single canonical JSON line (no trailing newline). Each sink is responsible for adding the LF separator between records.
func ToDagstackJSONLDict ¶
ToDagstackJSONLDict converts a LogRecord into a Go map ready for canonical JSON serialization. Per spec ADR-0001 §1 — dagstack JSON-lines wire format uses snake_case field names, lowercase hex trace_id/span_id, and integer nanoseconds for timestamps.
Empty / zero fields are omitted from the result for cleaner diagnostics:
- observed_time_unix_nano omitted when zero
- attributes omitted when empty
- instrumentation_scope omitted when nil
- resource omitted when nil or empty
- trace_id / span_id omitted when nil
- trace_flags omitted when zero
func ValidateRedactionConfig ¶ added in v0.2.0
func ValidateRedactionConfig(cfg RedactionConfig) error
ValidateRedactionConfig returns an error when cfg.ExtraSuffixes contains an entry that is empty, whitespace-bearing, or not lowercase ASCII. Per spec §10.4 the binding MUST reject these at Configure time.
Types ¶
type Attrs ¶
Attrs is a convenience alias for the per-record attribute map. Per spec ADR-0001 §1, attributes is a Map<string, Value> where Value is a recursive sum type: string | int | float | bool | nil | map[string]Value | []Value.
In Go we use any (interface{}) as the value type to avoid an unwieldy recursive type alias; runtime checks in the wire emitter validate that values are JSON-serializable.
func RedactAttributes ¶
RedactAttributes returns a new attribute map where values of keys matching the secret suffixes are replaced with RedactedPlaceholder. The redaction is recursive both for nested map[string]any values and for []any slices whose items are map[string]any (per spec §10.2). A secret key buried inside a list of maps (for example, an event-stream payload) is masked even though the list key itself is not secret.
The original attrs map is not mutated. Pass nil for suffixes to use DefaultSecretSuffixes.
type ConfigureOption ¶
type ConfigureOption func(*configureState)
ConfigureOption mutates the bootstrap state applied by Configure. Use the `With*` constructors below to build options; the option set is open for extension without breaking source compatibility.
func WithAutoInjectTraceContext ¶ added in v0.2.0
func WithAutoInjectTraceContext(enabled bool) ConfigureOption
WithAutoInjectTraceContext is the cross-binding parity flag declared in spec ADR-0001 v1.2 §3.4.2. The Go binding's idiomatic API surface is the explicit-ctx mode (Get(name).InfoCtx(ctx, ...) etc.) per §3.4.1; the auto-inject mode is **declared unsupported** in this binding to honour Go's no-implicit-context invariant.
Calling this option with `enabled=false` is a no-op and is provided so cross-binding configurations (Python / TypeScript) that explicitly set the flag to `false` can keep the Go-side configure call symmetric.
Calling with `enabled=true` panics at option-construction time per §3.4.1 — a binding without an ambient-context primitive MUST surface a configuration error rather than silently no-op. Use the *Ctx severity methods (InfoCtx / ErrorCtx / etc.) for explicit context propagation.
func WithPerLoggerLevels ¶
func WithPerLoggerLevels(levels map[string]any) ConfigureOption
WithPerLoggerLevels overrides min-severity for the listed logger names. Useful for silencing noisy upstreams (e.g., {"net/http": "WARN"}).
func WithRedactionConfig ¶ added in v0.2.0
func WithRedactionConfig(cfg RedactionConfig) ConfigureOption
WithRedactionConfig registers a Phase 1 redaction policy (spec §10.4). The configured suffix list applies to every emit through the root logger; child loggers inherit unless their own redaction is set later.
Validation runs at option-construction time — invalid suffixes (empty, whitespace, non-lowercase-ASCII) panic so the misconfiguration surfaces at startup, never at the first emit. This matches the Configure philosophy already used by WithRootLevel / WithPerLoggerLevels.
Default behaviour (no WithRedactionConfig call) keeps the 6-element DefaultSecretSuffixes set.
func WithResourceAttributes ¶
func WithResourceAttributes(attrs Attrs) ConfigureOption
WithResourceAttributes installs process/service-level attributes on the root logger Resource (per spec §4.2). Inherited by all loggers.
func WithRootLevel ¶
func WithRootLevel(level any) ConfigureOption
WithRootLevel sets the default minimum severity threshold for the root logger. The level argument may be a string (case-insensitive name like "INFO" or "warn") or an integer in the [1, 24] range.
func WithSinks ¶
func WithSinks(sinks ...Sink) ConfigureOption
WithSinks attaches the provided sinks to the root logger. Children inherit unless they declare their own sinks.
type ConsoleMode ¶
type ConsoleMode int
ConsoleMode selects the output style of ConsoleSink per spec ADR-0001 §7.2.
const ( // ConsoleAuto selects pretty when the stream is a TTY, json otherwise. ConsoleAuto ConsoleMode = iota // ConsoleJSON forces canonical JSON-lines output. ConsoleJSON // ConsolePretty forces single-line colored output (ANSI escape codes). ConsolePretty )
func (ConsoleMode) String ¶
func (m ConsoleMode) String() string
String returns the textual mode name (used in Sink.ID).
type ConsoleSink ¶
type ConsoleSink struct {
// contains filtered or unexported fields
}
ConsoleSink writes LogRecords to stdout/stderr as JSON-lines or pretty colored text. Per spec §7.2 it is a Phase 1 MVP sink; the default mode auto-detects TTY for the destination stream.
func NewConsoleSink ¶
func NewConsoleSink(mode ConsoleMode, stream io.Writer, minSeverity int) *ConsoleSink
NewConsoleSink constructs a ConsoleSink writing to stream in the given mode. When stream is nil, os.Stderr is used.
minSeverity sets the early-drop threshold: records with severity_number below this value are skipped (use 1 to accept everything).
func (*ConsoleSink) Close ¶
func (c *ConsoleSink) Close() error
Close marks the sink as closed and flushes the stream once. Subsequent Emit calls become no-ops. Close does not close stdout/stderr — those are shared with the process — only the close marker is set.
func (*ConsoleSink) Emit ¶
func (c *ConsoleSink) Emit(record *LogRecord)
Emit writes record to the underlying stream. Errors are absorbed; sink failures must not propagate to the caller of Logger.Info.
func (*ConsoleSink) Flush ¶
func (c *ConsoleSink) Flush(_ float64) error
Flush is a no-op for synchronous console writes; the stream is flushed after every Emit. timeoutSeconds is ignored.
func (*ConsoleSink) ID ¶
func (c *ConsoleSink) ID() string
ID returns the URI-style sink identifier ("console:json", "console:pretty", "console:auto").
func (*ConsoleSink) SupportsSeverity ¶
func (c *ConsoleSink) SupportsSeverity(severityNumber int) bool
SupportsSeverity reports whether severityNumber meets the configured minimum severity threshold.
type FileSink ¶
type FileSink struct {
// contains filtered or unexported fields
}
FileSink writes LogRecords to a local file as canonical JSON-lines, with optional size-based rotation per spec ADR-0001 §7.2.
SECURITY NOTE: the path argument is resolved with filepath.Abs and opened as-is — there is no allow-list, sanitization, or path-traversal check, and the open follows symlinks. The host MUST treat path as a trusted configuration value and never accept it directly from end-user input or a plugin manifest. If the application supports plugin-supplied logging configuration, enforce an allow-list of writable directories before constructing the sink, and consider symlink-resistant resolution (openat2 RESOLVE_NO_SYMLINKS or a manual walk-up + lstat check) upstream of the FileSink.
Phase 1 implementation uses a hand-rolled rotator (rather than the standard library logging package) — Go's log/slog and log packages do not expose a rotating file handler, and a clean implementation here keeps the dependency footprint minimal.
Rotation rules:
- When maxBytes > 0, the file is rotated after a write that would push its size beyond maxBytes.
- Rotation moves the current file to "<path>.1", existing ".N" files shift to ".N+1"; the file at index keep is removed.
- When maxBytes <= 0, rotation is disabled and the file grows unbounded.
func NewFileSink ¶
NewFileSink opens (creating if necessary) the file at path for append-only writes. Returns an error if the parent directory does not exist or the file cannot be opened.
maxBytes <= 0 disables rotation; keep is the number of archived files to retain (e.g., keep=2 keeps .1 and .2).
func (*FileSink) Emit ¶
Emit writes record as a JSON line. Errors are absorbed; sink failures must not propagate to the caller of Logger.Info.
func (*FileSink) Flush ¶
Flush attempts to sync the underlying file handle. timeoutSeconds is ignored — Phase 1 writes are synchronous.
func (*FileSink) SupportsSeverity ¶
SupportsSeverity reports whether severityNumber meets the minimum.
type FlushFailure ¶
FlushFailure pairs a sink id with the error it returned from Flush.
type FlushResult ¶
type FlushResult struct {
// Success is true when every effective sink flushed without error.
Success bool
// Partial is true when at least one (but not all) sinks flushed
// successfully.
Partial bool
// FailedSinks lists sinks that returned an error from Flush, with the
// underlying error for diagnostics.
FailedSinks []FlushFailure
}
FlushResult records the outcome of a Logger.Flush call. The returned shape mirrors the spec §13 contract; Phase 1 sinks always either succeed or fail with the underlying I/O error.
type InMemorySink ¶
type InMemorySink struct {
// contains filtered or unexported fields
}
InMemorySink accumulates LogRecords in a capacity-bounded ring buffer. Per spec ADR-0001 §7.2 — Phase 1 sink primarily used for tests and application self-checks.
The oldest records are dropped automatically when capacity is exceeded.
func NewInMemorySink ¶
func NewInMemorySink(capacity int, minSeverity int) *InMemorySink
NewInMemorySink constructs a ring buffer with the given capacity. A capacity <= 0 is treated as 1 to keep the sink alive.
func (*InMemorySink) Capacity ¶
func (s *InMemorySink) Capacity() int
Capacity returns the configured ring buffer size.
func (*InMemorySink) Clear ¶
func (s *InMemorySink) Clear()
Clear empties the captured-records buffer. The sink remains usable.
func (*InMemorySink) Close ¶
func (s *InMemorySink) Close() error
Close marks the sink as closed; subsequent Emit calls become no-ops. Captured records remain accessible via Records.
func (*InMemorySink) Emit ¶
func (s *InMemorySink) Emit(record *LogRecord)
Emit appends record to the ring; if at capacity, drops the oldest entry.
func (*InMemorySink) Flush ¶
func (s *InMemorySink) Flush(_ float64) error
Flush is a no-op for in-memory storage.
func (*InMemorySink) ID ¶
func (s *InMemorySink) ID() string
ID returns the URI-style sink identifier ("in-memory:cap=<N>#<seq>").
func (*InMemorySink) Records ¶
func (s *InMemorySink) Records() []*LogRecord
Records returns a snapshot copy of the captured records. Mutating the returned slice does not affect the sink's internal buffer.
func (*InMemorySink) SupportsSeverity ¶
func (s *InMemorySink) SupportsSeverity(severityNumber int) bool
SupportsSeverity reports whether severityNumber meets the minimum.
type InstrumentationScope ¶
type InstrumentationScope struct {
Name string `json:"name"`
Version string `json:"version,omitempty"`
Attributes Attrs `json:"attributes,omitempty"`
}
InstrumentationScope describes the logger that emitted a LogRecord. Per spec ADR-0001 §4.1 — name + version + optional attrs.
Name matches the logger name (e.g., "dagstack.rag.retriever"); version is the semantic version of the package or plugin.
type LogRecord ¶
type LogRecord struct {
// TimeUnixNano is the emit time in nanoseconds since the Unix epoch.
TimeUnixNano int64
// SeverityNumber is the OTel severity in the [1, 24] range.
SeverityNumber int
// SeverityText is one of the 6 canonical strings (see CanonicalSeverityTexts).
SeverityText string
// Body is the primary message payload. May be a string or a structured
// value (map/array/scalar) — represented as any.
Body any
// Attributes is the per-record key-value context.
Attributes Attrs
// InstrumentationScope is the logger self-descriptor (§4.1). May be nil
// for direct LogRecord construction outside the Logger API.
InstrumentationScope *InstrumentationScope
// Resource is the process/service/host attribute set (§4.2). May be nil.
Resource *Resource
// TraceID is the W3C Trace Context — 16 random bytes when an active span
// is present; nil otherwise.
TraceID []byte
// SpanID is the W3C Trace Context — 8 random bytes when an active span
// is present; nil otherwise.
SpanID []byte
// TraceFlags is the W3C flags byte (sampled bit etc.). Zero when no
// active span.
TraceFlags uint8
// ObservedTimeUnixNano is the ingest time at the sink, in nanoseconds.
// The producer leaves this 0; the sink fills it in (per spec §1).
ObservedTimeUnixNano int64
}
LogRecord is the OTel Log Data Model v1.24-compatible log record.
Per spec ADR-0001 §1: internal field names match the OTel normative spec (TimeUnixNano, ObservedTimeUnixNano, SeverityNumber, SeverityText, Body, Attributes, Resource, InstrumentationScope, TraceId as 16 bytes, SpanId as 8 bytes, TraceFlags).
Wire serialization (OTLP / JSON) lives in separate functions, see wire.go:
- dagstack JSON-lines: snake_case keys, hex trace/span ids, raw int timestamps.
- OTel JSON (Phase 2+): camelCase keys, stringified int timestamps.
- OTLP protobuf (Phase 2+): native OTel wire.
ObservedTimeUnixNano — the sink sets it on ingestion when zero (per spec §1 ownership rule).
type Logger ¶
type Logger struct {
// contains filtered or unexported fields
}
Logger is the primary handle for emitting LogRecords. Per spec ADR-0001 §3 it provides named loggers with dot-hierarchy, severity emits, child bindings, and scoped sink overrides.
Construct via Get — direct construction is reserved for the binding internals. The hierarchy is the dot-prefix of the name:
"dagstack.rag.retriever" → parent "dagstack.rag" → "dagstack" → root ""
Sinks and min-severity inherit from the parent unless overridden on the child via SetSinks / SetMinSeverity.
Context propagation reads OTel trace state from a context.Context. Use the *Ctx variants (InfoCtx, ErrorCtx, ...) to enable trace_id/span_id auto-injection; the non-Ctx variants (Info, Error, ...) skip propagation.
func Get ¶
Get returns the cached logger with the given name; if absent, creates one and links it into the parent chain. Pass an empty name to obtain the root logger; pass a non-empty version to associate it with the instrumentation_scope.
Repeated Get calls with the same name return the same instance. To attach or update an instrumentation-scope version, use GetVersioned.
func GetVersioned ¶
GetVersioned returns the cached logger for name, attaching or updating the supplied instrumentation-scope version on the singleton. Calling GetVersioned with the same name and a different version updates the existing logger's scope in place.
func (*Logger) AppendSinks ¶
AppendSinks returns a detached child logger whose sink list is the parent chain's effective sinks plus the supplied extras.
func (*Logger) Child ¶
Child returns a detached child logger with the supplied attributes pre-bound to every record. Child-bound attrs are merged before call-site attrs, so call-site values win on collision.
func (*Logger) Close ¶
Close calls Close on every effective sink. Errors are aggregated into the returned slice; Close on an already-closed sink is a no-op.
func (*Logger) EffectiveMinSeverity ¶
EffectiveMinSeverity resolves the early-drop threshold — explicit on this logger or inherited.
func (*Logger) EffectiveResource ¶
EffectiveResource resolves the Resource — explicit or inherited.
func (*Logger) EffectiveSecretSuffixes ¶ added in v0.2.0
EffectiveSecretSuffixes resolves the redaction-suffix list applied by this logger — explicit on this node or inherited from the parent chain. Returns DefaultSecretSuffixes when no override is registered anywhere up the chain. An explicit empty list (disable-all per spec §10.4) is preserved through inheritance — the returned slice is non-nil zero-length, distinguishable from "no override" via the suffixesExplicit flag on the resolving node.
The returned slice is a snapshot copy; mutations do not affect the logger.
TODO(#105): collapse the four chain-walks (sinks / min-severity / resource / suffixes) into one upward traversal — current impl acquires N locks per emit per dimension.
func (*Logger) EffectiveSinks ¶
EffectiveSinks resolves the sink list — explicit on this logger or inherited from the parent chain.
func (*Logger) Exception ¶
Exception emits an ERROR-severity record with OTel exception.* attributes per spec §3.2 — exception.type, exception.message, exception.stacktrace.
The stacktrace is captured via runtime/debug.Stack at the call site; for errors that wrap a stack via errors.New / fmt.Errorf the captured stack shows the logging call point (the most useful frame for triage).
body may be nil — in that case err.Error() is used as the LogRecord body. Extra attrs override exception.* keys when supplied.
func (*Logger) ExceptionCtx ¶
ExceptionCtx emits an ERROR record with OTel exception.* attributes plus trace context propagation from ctx.
func (*Logger) Flush ¶
func (l *Logger) Flush(timeoutSeconds float64) (*FlushResult, error)
Flush attempts to flush every effective sink. timeoutSeconds is forwarded to each Sink.Flush; the global Flush itself does not enforce a deadline, so a cooperatively-implemented sink keeps the budget honest.
Phase 1: every built-in sink (ConsoleSink, FileSink, InMemorySink) is synchronous, so timeoutSeconds is accepted for forward compatibility but is NOT enforced — none of the built-in sinks ever return a timeout error. Phase 2 (OTLPSink and friends) MUST honour the deadline and return a wrapped context.DeadlineExceeded; see spec ADR-0001 §7.1.
func (*Logger) Log ¶
Log emits a record with the explicit severity_number (must be in [1, 24]). Use this for intermediate values like TRACE2 or INFO3 that share a severity_text bucket but a different numeric granularity.
func (*Logger) LogCtx ¶
LogCtx is the generic emitter with explicit severity and context-aware trace propagation.
func (*Logger) OnReconfigure ¶
func (l *Logger) OnReconfigure(_ func()) *Subscription
OnReconfigure registers a callback to fire when the logger's effective configuration changes. Phase 1 watch-based reconfigure is not implemented — the returned Subscription has Active=false and the callback never fires.
func (*Logger) ScopeSinks ¶
func (l *Logger) ScopeSinks(ctx context.Context, sinks []Sink, fn func(context.Context) error) error
ScopeSinks runs fn with a temporary sink override on this logger, restoring the previous sink set (and its explicit/inherit state) on return. Per spec §6.2 the Go idiom is the callback form — it pairs with context.Context and does not require defer at the call site.
The override is applied to this logger instance directly, so any goroutines emitting through Logger.Get(name) during fn observe the same sinks. fn returns its error value; ScopeSinks does not modify it.
func (*Logger) SetMinSeverity ¶
SetMinSeverity sets the explicit early-drop threshold for this logger.
Mutates the shared registry node (visible to all consumers of the same name). Children inherit unless they set their own threshold.
func (*Logger) SetRedactionSuffixes ¶ added in v0.2.0
SetRedactionSuffixes installs the effective secret-suffix list on this logger (typically called on the root logger via Configure → spec §10.4).
Mutates the shared registry node. The suffix list MUST already be validated and lowercased — use BuildEffectiveSuffixes to derive it from a RedactionConfig.
Pass nil to fall back to inherited behaviour (parent's suffixes or DefaultSecretSuffixes at the root).
func (*Logger) SetResource ¶
SetResource installs an explicit Resource on this logger.
Mutates the shared registry node (visible to all consumers of the same name). Children inherit unless they set their own Resource.
func (*Logger) SetSinks ¶
SetSinks installs an explicit sink list on this logger.
Mutates the shared registry node (visible to all consumers of the same name). Children inherit unless they set their own sinks.
func (*Logger) TraceCtx ¶
TraceCtx emits a TRACE record with trace_id/span_id auto-injected from ctx.
func (*Logger) WithSinks ¶
WithSinks returns a detached child logger whose sink list is replaced with the supplied set. The child is not cached in the global registry.
func (*Logger) WithoutSinks ¶
WithoutSinks returns a detached child logger with an empty sink list — emits go to /dev/null. Useful for silencing a sub-tree of operations.
type RedactionConfig ¶ added in v0.2.0
type RedactionConfig struct {
// ExtraSuffixes are additional secret suffixes registered by the
// application. Each entry MUST be lowercase ASCII, contain no
// whitespace, and be non-empty (validated at option-construction time).
ExtraSuffixes []string
// ReplaceDefaults, when true, swaps the base set for ExtraSuffixes
// instead of unioning. With ReplaceDefaults=true and an empty
// ExtraSuffixes list, all suffix-based redaction is disabled — the
// binding emits a WARN diagnostic on dagstack.logger.internal in
// that case (spec §10.4 disable-all warning).
ReplaceDefaults bool
}
RedactionConfig is the public Phase 1 surface for tuning suffix-based redaction (spec ADR-0001 §10.4). Applications register a config via WithRedactionConfig at Configure time.
The zero value (no extras, ReplaceDefaults=false) keeps the Phase 1 baseline: the 6-element DefaultSecretSuffixes set is applied with no additions. Calls without WithRedactionConfig keep the same baseline.
type Resource ¶
type Resource struct {
Attributes Attrs `json:"attributes,omitempty"`
}
Resource carries process/service/host-level attributes shared across all loggers in the same process (OTel Resource). Per spec ADR-0001 §4.2 — typical keys: service.name, service.version, deployment.environment, host.name, process.pid, telemetry.sdk.{name,version,language}.
type Severity ¶
type Severity int
Severity is the OTel severity_number, an integer in the inclusive range [1, 24]. Per spec ADR-0001 §2:
- 1-4 bucket → severity_text "TRACE"
- 5-8 bucket → severity_text "DEBUG"
- 9-12 bucket → severity_text "INFO" (default INFO=9)
- 13-16 bucket → severity_text "WARN"
- 17-20 bucket → severity_text "ERROR"
- 21-24 bucket → severity_text "FATAL"
Backends filter by exact match on severity_text, so the canonical 6-string set never grows. Numeric granularity (TRACE2, INFO3, ...) is carried in severity_number; intermediate values go through Logger.Log.
type Sink ¶
type Sink interface {
// ID returns a URI-style identifier for diagnostics.
ID() string
// Emit delivers a record to the sink. Phase 1: synchronous; Phase 2: enqueue.
// Errors during emit are absorbed by the sink and surfaced via metrics or
// the dagstack.logger.internal diagnostic channel — Emit itself never
// blocks the caller and never returns an error.
Emit(record *LogRecord)
// Flush blocks until buffered records are delivered, or until the
// timeout is exhausted. Phase 1 sinks are synchronous, so
// timeoutSeconds is accepted for forward compatibility but is NOT
// enforced — Phase 1 implementations never return a timeout error.
// Phase 2 (OTLPSink and friends) MUST honour the deadline and
// return a wrapped context.DeadlineExceeded.
Flush(timeoutSeconds float64) error
// Close flushes pending records and releases resources. Idempotent.
Close() error
// SupportsSeverity is the early-drop hint: returns false when the sink
// will not emit a record at the given severity_number. Logger uses this
// to avoid building a record for sinks that would discard it.
SupportsSeverity(severityNumber int) bool
}
Sink is the destination for LogRecords per spec ADR-0001 §7.1.
Implementations must be safe for concurrent use. Phase 1 sinks (ConsoleSink, FileSink, InMemorySink) use synchronous local I/O — the non-blocking property of Logger.Info is provided by OS buffering. Phase 2 sinks (OTLPSink, ...) will introduce true async batching with internal queues; Emit then enqueues the record for a worker.
ID is a URI-style identifier used in diagnostics and metrics:
"console:json" "file:/var/log/app.jsonl" "in-memory:cap=100#1"
type Subscription ¶
type Subscription struct {
// Path echoes the subscription path for introspection.
Path string
// Active is true iff a watch-capable backend is registered AND covers
// the subscription path. Always false in Phase 1.
Active bool
// InactiveReason carries a human-readable diagnostic when Active=false.
// Phase 1 sets it to a fixed message:
//
// "Phase 1 logger does not support watch-based reconfigure"
InactiveReason string
// contains filtered or unexported fields
}
Subscription is a placeholder handle returned by Logger.OnReconfigure per spec ADR-0001 §7.2.
In Phase 1 watch-based reconfigure is not implemented — every subscription is constructed with Active=false and InactiveReason populated. The handle is forward-compatible: when Phase 2 introduces a Watcher (file or admin API), the same Subscription type carries the active subscription with a real Unsubscribe callback.
func NewInactiveSubscription ¶
func NewInactiveSubscription(path, reason string) *Subscription
NewInactiveSubscription constructs a Subscription whose Active field is false. Use this to signal that a subscription was accepted but will never fire — typically because watch is not implemented in this binding phase.
func NewSubscription ¶
func NewSubscription(path string, unsubscribe func()) *Subscription
NewSubscription constructs an active Subscription bound to the given cancellation callback. Reserved for Phase 2+ Watcher implementations.
func (*Subscription) Unsubscribe ¶
func (s *Subscription) Unsubscribe()
Unsubscribe cancels the subscription. Idempotent — subsequent calls are no-op. After Unsubscribe returns, the callback is guaranteed not to fire.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package docs_examples contains automated tests for the Go snippets in dagstack-logger-docs.
|
Package docs_examples contains automated tests for the Go snippets in dagstack-logger-docs. |