Documentation
¶
Overview ¶
Package logger — defines the AttrValue type — an immutable key/value pair attached to a RecordEvent. The Value field is now a kind-discriminated union (see value.go) instead of an open `any`, so handlers dispatch on the Kind enum and pay no boxing cost on the hot path.
Package logger — declares the Encoder port — the format-side boundary that mirrors Sink (transport-side). Both ports live in core/ per hexagonal architecture conventions; concrete adapters live under internal/service/logger/encoder/. See ADR 0005.
Package logger — declares the Handler interface that concrete implementations in sdk/internal/service satisfy. Handlers format RecordEvent values and write them to the backing sink.
Package logger — declares the Kind enum that discriminates the union payload carried by Value. Handlers switch on Kind to select the matching accessor (String, Int64, Float64, …) instead of paying the cost of an `any` type assertion at every render call.
Package logger — declares the Logger interface — the primary entry point consumers interact with. A Logger binds a Handler and exposes ergonomic Log / With / Enabled operations over it.
Package logger — defines the RecordEvent value carried between Logger and Handler. It is an immutable snapshot of a single log event at the core boundary.
Package logger — declares the Sink port — the transport-side boundary of the logger architecture. A Sink receives a fully formatted byte payload (typically produced by an Encoder) plus the originating RecordEvent for sinks that need structured access (CloudWatch metadata, S3 object tags, syslog severity mapping).
Concrete Sink implementations live in internal/service/logger/sink/<x>/ (console, file, multi, async, route, failover, sample, recover, syslog, …). Encoders live in internal/service/logger/encoder/. The Handler composes one Encoder with one Sink — see commit 7 for the genericHandler that wires them together.
Package logger — declares the trace-correlation half of a log record: the TraceContextValue a RecordEvent carries, and the TraceContextSource port that reads one off a context.Context.
The pair lives here rather than in internal/core/trace because a log record is not a span: it borrows two identifiers from one. Keeping the value local keeps this package stdlib-only — importing the trace domain would put its whole model (and core/metrics behind it) in front of every consumer that only wants a line on stderr. The binding between the two lives at the top layer, in pkg/v1/logger, which is allowed to know both domains. See ADR 0062.
Package logger — declares the Value type — a discriminated union carrying the payload of an AttrValue without forcing every concrete type through `any`. Handlers switch on Value.Kind() to select a typed accessor (Int64, Float64, String, …) and skip the cost of reflection at format time.
Storage layout: bool / int64 / uint64 / float64 / time.Duration share the `num` field via bit-packing (math.Float64bits for floats, two's complement for ints). Strings live in `str`. Time, Group and Any payloads live in `any`. The zero Value carries Kind == KindAny with a nil `any` payload.
Index ¶
- Constants
- type AttrValue
- type Encoder
- type Handler
- type Kind
- type Logger
- type RecordEvent
- type Sink
- type TraceContextSource
- type TraceContextValue
- type Value
- func AnyValue(v any) Value
- func BoolValue(v bool) Value
- func DurationValue(v time.Duration) Value
- func Float64Value(v float64) Value
- func GroupValue(attrs ...AttrValue) Value
- func Int64Value(v int64) Value
- func IntValue(v int) Value
- func NewValue(v any) Value
- func StringValue(v string) Value
- func TimeValue(v time.Time) Value
- func Uint64Value(v uint64) Value
- func (v Value) Any() any
- func (v Value) Bits() packedBits
- func (v Value) Bool() bool
- func (v Value) Duration() time.Duration
- func (v Value) Float64() float64
- func (v Value) Group() []AttrValue
- func (v Value) Int64() int64
- func (v Value) Kind() Kind
- func (v Value) String() string
- func (v Value) Time() time.Time
- func (v Value) Uint64() uint64
Constants ¶
const ( // TraceIDLen is the trace identifier's length in bytes. TraceIDLen int = 16 // SpanIDLen is the span identifier's length in bytes. SpanIDLen int = 8 // TraceIDHexLen is the trace identifier's length in lowercase hex. TraceIDHexLen int = TraceIDLen * 2 // SpanIDHexLen is the span identifier's length in lowercase hex. SpanIDHexLen int = SpanIDLen * 2 )
TraceIDLen and SpanIDLen are the byte lengths W3C Trace Context fixes for the two identifiers: 16 bytes of trace-id, 8 of span-id. They are not tunable — they are the format, not a default.
const ( // TraceIDKey is the top-level field name carrying the trace identifier. TraceIDKey string = "trace_id" // SpanIDKey is the top-level field name carrying the span identifier. SpanIDKey string = "span_id" )
TraceIDKey and SpanIDKey are the field names OpenTelemetry prescribes for trace context in NON-OTLP log formats — "use the field names trace_id for the TraceId, span_id for the SpanId" (specification/compatibility/logging_trace_context.md). They are top-level keys of the record, not attributes, which is why they are rendered by the encoders rather than appended to RecordEvent.Attrs.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AttrValue ¶
type AttrValue struct {
// Key identifies the attribute in the output line.
Key string
// Value carries the typed payload; handlers dispatch on Value.Kind().
Value Value
}
AttrValue is an immutable key/value pair carried by a RecordEvent. Use the typed Value constructors (StringValue, Int64Value, …) to build the Value field — the zero Value is a valid KindAny carrying nil.
type Encoder ¶
type Encoder interface {
// Name returns the canonical encoder identifier ("text", "ndjson",
// "json"). Useful for diagnostic dumps and metrics tagging.
Name() (name string)
// Append serialises r onto dst and returns the (possibly re-allocated)
// buffer. groups carries the active group prefix stack — encoders are
// responsible for rendering it as their format-specific prefix
// ("g1.g2.key" for text, nested objects for JSON).
Append(dst []byte, groups []string, r RecordEvent) (out []byte)
}
Encoder formats a RecordEvent into a byte slice. Implementations MUST be safe for concurrent use by multiple goroutines and SHOULD avoid allocating beyond the caller-supplied buffer (typically borrowed from the kernel/buffer pool) so the encoding step itself does not allocate.
type Handler ¶
type Handler interface {
// Enabled reports whether the handler would emit a record at the given
// level, allowing callers to skip expensive attribute construction.
Enabled(ctx context.Context, r RecordEvent) (enabled bool)
// Handle formats and writes a RecordEvent. Returning a non-nil error
// signals a sink failure; the Logger may drop the record regardless.
Handle(ctx context.Context, r RecordEvent) (err error)
// WithAttrs returns a derived Handler that prepends the given attrs to
// every subsequent RecordEvent. The receiver MUST NOT be mutated.
WithAttrs(attrs []AttrValue) (child Handler)
// WithGroup returns a derived Handler that namespaces every subsequent
// attribute key under the given group name (rendered as "group.key"
// for text handlers, or as a nested object for structured handlers).
// An empty name MUST be treated as a no-op so callers can pass user
// input without guarding it.
WithGroup(name string) (child Handler)
}
Handler formats and emits RecordEvent values produced by a Logger. Implementations MUST be safe for concurrent use by multiple goroutines.
type Kind ¶
type Kind int8
Kind discriminates the payload variant carried by a Value. Kind values are stable across the public API; new variants are appended at the end so old handlers default to KindAny rather than panic on an unknown discriminant.
const KindAny Kind = 0
KindAny tags a Value whose payload is an unconstrained interface; handlers SHOULD render it via fmt.Sprintf or a "?" placeholder when the concrete type is not in their format table. It is also the zero-value of Kind.
const KindBool Kind = 1
KindBool tags a Value carrying a boolean payload.
const KindDuration Kind = 2
KindDuration tags a Value carrying a time.Duration payload.
const KindFloat64 Kind = 3
KindFloat64 tags a Value carrying a float64 payload.
const KindGroup Kind = 8
KindGroup tags a Value whose payload is a slice of AttrValue, used to model nested groups (slog.Group) without flattening into the parent record.
const KindInt64 Kind = 4
KindInt64 tags a Value carrying an int64 payload (also covers int / int32).
const KindString Kind = 5
KindString tags a Value carrying a string payload.
const KindTime Kind = 6
KindTime tags a Value carrying a time.Time payload.
const KindUint64 Kind = 7
KindUint64 tags a Value carrying an unsigned 64-bit integer payload.
type Logger ¶
type Logger interface {
// Log emits a RecordEvent at the given level with the supplied message
// and attrs. Implementations SHOULD short-circuit when Enabled returns
// false to avoid unnecessary formatting work.
Log(ctx context.Context, lv level.Level, msg string, attrs ...AttrValue)
// With returns a derived Logger whose emitted records always include the
// supplied attrs. The receiver MUST NOT be mutated.
With(attrs ...AttrValue) (child Logger)
// WithGroup returns a derived Logger whose subsequent attributes are
// namespaced under the given group name. Empty names MUST be a no-op.
WithGroup(name string) (child Logger)
// Enabled reports whether a record at the given level would be emitted.
Enabled(ctx context.Context, lv level.Level) (enabled bool)
}
Logger is the primary logging interface. Implementations MUST be safe for concurrent use by multiple goroutines.
type RecordEvent ¶
type RecordEvent struct {
// Time is the instant the event occurred; zero means "fill in at handle time".
Time time.Time
// Level is the severity of the event.
Level level.Level
// Message is the human-readable description of the event.
Message string
// PC is the program counter captured at the Log call site (via
// runtime.Callers). Zero means "no caller information available";
// handlers SHOULD resolve PC into a frame lazily at format time.
PC uintptr
// Attrs carries the structured key/value pairs attached to this event.
Attrs []AttrValue
// TraceContext is the identity of the span this event was emitted
// inside; the zero value means "no trace here" and MUST render nothing.
// It is a FIELD rather than two entries in Attrs because the Encoder
// port receives no context.Context — the identity has to travel on the
// record to reach the formatter at all — and because OpenTelemetry
// prescribes trace_id / span_id as TOP-LEVEL keys of the log object,
// which an attribute cannot be: WithGroup would prefix it. See ADR 0062.
TraceContext TraceContextValue
}
RecordEvent captures a single log event; handlers are responsible for writing or forwarding it. Instances are intended to be used read-only after construction by the Logger implementation.
type Sink ¶
type Sink interface {
// Write delivers the formatted byte payload p plus the originating record
// to the underlying transport. Sinks MAY ignore p and re-serialise from
// r when their wire protocol differs from the encoder's output (e.g. a
// CloudWatch sink reads r.Time directly to populate the AWS request).
Write(ctx context.Context, r RecordEvent, p []byte) (n int, err error)
// Flush forces any buffered records out. A no-op for synchronous sinks.
Flush(ctx context.Context) (err error)
// Close releases any resources held by the sink (file descriptors, AWS
// clients, network connections). Sinks MAY refuse subsequent Writes
// after Close returns; callers MUST NOT use the sink after closing it.
Close() (err error)
}
Sink is the transport port consumed by the generic Handler. Implementations MUST be safe for concurrent use by multiple goroutines and SHOULD assume their own concurrency model (mutex / lock-free / batched flush) so the Handler can stay encoder-agnostic. Sinks that need the severity level read it directly from r.Level — the port is intentionally kept narrow.
type TraceContextSource ¶ added in v0.1.27
type TraceContextSource func(ctx context.Context) (trace TraceContextValue)
TraceContextSource reads the trace context carried by ctx.
It is a FUNC port, so ADR 0039 is satisfied structurally: a func type cannot grow a method, so publishing it can never break a downstream implementation. It returns the zero (invalid) value rather than an error or an ok flag — running outside a trace is the normal state of most code, not a fault, and IsValid is the single question every caller already has to ask.
Implementations MUST NOT allocate: they run once per emitted record.
type TraceContextValue ¶ added in v0.1.27
type TraceContextValue struct {
// TraceID identifies the whole trace; rendered as 32 lowercase hex digits.
TraceID [TraceIDLen]byte
// SpanID identifies the span within the trace; 16 lowercase hex digits.
SpanID [SpanIDLen]byte
}
TraceContextValue is the trace correlation a single log record carries: the identity of the span the record was emitted inside.
It is a VALUE and carries no flags and no tracestate, because a log line answers exactly one question — "which span produced this?" — and the sampling decision that governs the trace is not a property of the line.
The zero value is the INVALID context, deliberately: an all-zero trace-id is what W3C Trace Context §3.2.2.3 declares invalid, and an all-zero span-id is §3.2.2.4's. So "no trace here" and "a trace nobody may join" are one value with one spelling, and every consumer has exactly one check to make.
func (TraceContextValue) AppendSpanIDHex ¶ added in v0.1.27
func (c TraceContextValue) AppendSpanIDHex(dst []byte) []byte
AppendSpanIDHex appends the span identifier to dst as 16 lowercase hex digits and returns the extended buffer. Same contract as AppendTraceIDHex.
func (TraceContextValue) AppendTraceIDHex ¶ added in v0.1.27
func (c TraceContextValue) AppendTraceIDHex(dst []byte) []byte
AppendTraceIDHex appends the trace identifier to dst as 32 LOWERCASE hex digits and returns the extended buffer.
The spelling is normative — "Trace IDs and span IDs must be lowercase and hex-encoded" (specification/compatibility/logging_trace_context.md) — and three formatters (the text encoder, the JSON encoder and the legacy text handler) have to agree on it byte for byte, which is why it lives beside the value rather than three times over in internal/service/logger.
It appends and never returns a string: a string would be one heap allocation per emitted record, and the logger's one-allocation-per-emit contract has no room for a second. See ADR 0062 and pkg/v1/logger/BENCH.md.
func (TraceContextValue) IsValid ¶ added in v0.1.27
func (c TraceContextValue) IsValid() bool
IsValid reports whether the pair names a span a backend can join: both identifiers present and non-zero.
Both are required rather than either, and that follows the data model rather than convenience: "if a SpanId is provided, the corresponding TraceId should also be included" (specification/logs/data-model.md, Trace Context Fields). The only producer in this SDK is a W3C span context, which is itself invalid unless both identifiers are set, so a half-populated pair never reaches an encoder through a supported path — and if one is forged by hand, emitting half of it would produce a field no query can join on.
type Value ¶
type Value struct {
// contains filtered or unexported fields
}
Value is an immutable discriminated payload attached to an AttrValue. Use the documented constructors (StringValue / Int64Value / …) to obtain a well-formed instance — the zero value is a valid KindAny carrying nil.
func AnyValue ¶
AnyValue builds a Value of KindAny carrying an arbitrary payload. Handlers inspect the concrete type via type assertion and fall back to "?" when the type is not in their format table.
func DurationValue ¶
DurationValue builds a Value of kind duration. Storing the int64 nanosecond count in bits keeps the hot path allocation-free.
func GroupValue ¶
GroupValue builds a Value whose payload is a slice of AttrValue, modelling a nested attribute group. The slice is stored verbatim — callers MUST NOT mutate it after the Value is constructed.
func Int64Value ¶
Int64Value builds a Value of kind int64. int and int32 callers should widen to int64 at the call site.
func IntValue ¶
IntValue builds a Value of kind int64 from a plain int. Provided for caller ergonomy so the most common integer type does not need an explicit cast.
func NewValue ¶
NewValue is a generic alias of AnyValue, exposed for tooling that expects a New-prefixed constructor on every exported type. Prefer the typed constructors (StringValue, Int64Value, …) at call sites.
func TimeValue ¶
TimeValue builds a Value of kind time. The time.Time payload lives in the any field because its locale + monotonic representation does not fit into num without additional storage.
func (Value) Any ¶
Any returns the opaque payload for KindAny. For typed Kinds the return is nil — callers should call the typed accessor instead.
func (Value) Bits ¶
func (v Value) Bits() packedBits
Bits returns the raw uint64 storage that backs all bit-packed Kinds (KindBool / KindInt64 / KindUint64 / KindFloat64 / KindDuration). Callers SHOULD prefer the typed accessors; Bits exists for tooling and tests that need direct access to the packed storage.
func (Value) Bool ¶
Bool returns the boolean payload. The result is undefined when Kind is not KindBool; callers MUST guard with Kind() before calling this accessor.
func (Value) Duration ¶
Duration returns the time.Duration payload. The result is undefined when Kind is not KindDuration; callers MUST guard with Kind() before calling this accessor.
func (Value) Float64 ¶
Float64 returns the float64 payload. The result is undefined when Kind is not KindFloat64; callers MUST guard with Kind() before calling this accessor.
func (Value) Group ¶
Group returns the nested AttrValue slice. The result is undefined when Kind is not KindGroup; callers MUST guard with Kind() before calling this accessor. A nil any field decodes as a nil slice.
func (Value) Int64 ¶
Int64 returns the int64 payload. The result is undefined when Kind is not KindInt64; callers MUST guard with Kind() before calling this accessor.
func (Value) Kind ¶
Kind returns the discriminator for this Value, indicating which typed accessor (String / Int64 / Float64 / …) handlers should call.
func (Value) String ¶
String returns the textual payload when the Value carries KindString; for any other Kind it returns an empty string. Handlers that need a stringified rendering of non-string Kinds MUST format from the typed accessor instead.