encoder

package
v0.3.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 13, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package encoder provides concrete Encoder implementations (Text today; NDJSON and JSON to land in follow-up commits). The Encoder interface itself lives in internal/core/logger — this package is implementation- only, exposing the interface via a type alias so existing call sites that import "service/logger/encoder".Encoder keep compiling.

Per ADR 0005 hexagonal layering: core/ holds ports, service/ holds adapters. Previously Encoder lived here, creating an asymmetry where Sink lived in core/ and Encoder lived in service/ even though both are ports of the Handler.

Package encoder — implements jsonEncoder, the structured single-line JSON encoder that renders a record as one encoding/json-compatible object per line: {"ts":…,"level":…,"msg":…,<flat attrs>}\n. Lives beside the text encoder so the format and the transport (Sink) evolve independently. The byte writer is hand-rolled (append-based, no reflection, no json.Marshal) so the hot path stays allocation-free.

Package encoder — implements TextEncoder, the default human-readable encoder that renders a record as "TIME LEVEL msg key=val key=val …\n". Lives in its own package so the format and the transport (Sink) can evolve independently.

Package encoder — renders the record timestamp, the one field every encoded line carries and the single most expensive thing either encoder does.

A CPU profile of a bare emit attributed 34.6 % of the WHOLE call — builder, handler, encoder and sink together — to time.Time.AppendFormat, with time.nextStdChunk and time.appendInt the two largest flat entries under it. That is the generic formatter re-parsing the layout string, chunk by chunk, on every single log record, to reach a result that never varies in shape. appendTimestamp writes the same bytes with fixed offsets instead, and is measured at 6.0× the stdlib call on a UTC instant and 5.0× on an offset zone — `BenchmarkAppendTimestamp{,Zoned}` against their `…Stdlib` controls, reported in internal/service/logger/encoder/BENCH.md §5.2.

It is a REPLACEMENT for one exact layout, not a general formatter: anything it cannot render identically falls back to AppendFormat rather than approximating. TestAppendTimestampMatchesAppendFormat pins the equivalence across four zones and a hundred thousand instants each.

Index

Constants

View Source
const ReservedPrefix string = "attr."

ReservedPrefix is what a TOP-LEVEL attribute whose key the SDK reserves is rendered under, so the two never collide on one line. The whole namespace under it is reserved with it — see ReservesKey.

Variables

This section is empty.

Functions

func AppendSanitized added in v0.1.27

func AppendSanitized(dst []byte, text string) []byte

AppendSanitized copies text onto dst replacing '\n', '\r', and NUL with a single space so the content can never inject a new frame in a line-framed downstream sink. Other control characters are preserved to keep the rendering faithful; only framing-sensitive bytes are stripped. It scrubs every attacker-influenceable textual field — the Message, group names, and attribute keys — not just the Message (V110).

It is exported for one caller outside this package: the legacy service/logger.TextHandler, which renders the same line shape without going through an Encoder. That handler once appended all three fields verbatim — the scrub existed, but only here — so a line break in any of them forged a log line. One function shared by both renderers cannot drift from itself.

func ReservesKey added in v0.1.28

func ReservesKey(key string) bool

ReservesKey reports whether a TOP-LEVEL attribute key must be renamed under ReservedPrefix: the two the SDK emits as fields of its own — trace_id and span_id, the correlation OpenTelemetry prescribes and ADR 0062 writes on every record emitted inside a span — and anything already in the reserved namespace.

Two members of one JSON object cannot both be "trace_id" — a decoder keeps one of them, and which one is the decoder's business — so an attribute carrying that key beside the SDK's field could replace the correlation on the way in, and could carry a value from anywhere. It is renamed rather than dropped: nothing a caller logged is lost, and nothing it logged can be mistaken for the span this line came from.

The namespace is reserved WITH the two keys because the rename has to be injective, and prefixing alone is not: a caller logging both trace_id and a literal attr.trace_id would otherwise land both on "attr.trace_id" and the ambiguity would survive one name over. Prefixing anything already inside the namespace makes the mapping one-to-one — attr.trace_id becomes attr.attr.trace_id — so two distinct keys always render distinct.

Inside a group the key is already prefixed ("http.trace_id"), so it cannot collide and is left alone.

Types

type Encoder

type Encoder = corelogger.Encoder

Encoder is the format-side port now rooted in internal/core/logger. Kept as an alias here so consumers that import service/logger/encoder for the interface type keep compiling. New code should import the canonical type from core/logger directly.

func NewJSON

func NewJSON(clk clock.Clock) Encoder

NewJSON builds an Encoder that renders records as single-line JSON. Pass clock.System for production use; tests inject a fake clock. A nil clock falls back to the real wall clock so callers can pass clock.System or nil interchangeably. IFACE-PLUGIN: returns the Encoder contract so callers depend on the public surface; the concrete jsonEncoder is intentionally hidden.

func NewText

func NewText(clk clock.Clock) Encoder

NewText builds an Encoder bound to the supplied clock. Pass clock.System for production use; tests inject a fake clock. A nil clock falls back to the real wall clock so callers can pass clock.System or nil interchangeably. IFACE-PLUGIN: returns the Encoder contract so callers depend on the public surface; the concrete textEncoder is intentionally hidden.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL