Documentation
¶
Overview ¶
Package log is the one structured event line every part writes BESIDE the one human line it already writes. SPEC-LOGS.md Part 2 fixes the shape: one JSON object per state change, one line, through Go's own log/slog with slog.NewJSONHandler. The stdout line stays the SPEC.md event, unchanged; this line is the same event with the fields a LogQL query needs, so a person can ask "why is this card hung" without an ssh and a grep.
The fields are fixed, all fifteen of them, and every object carries all fifteen: an id that is not this event's scope is the empty string or zero rather than an omitted key, so `| json` never guesses and a query on `card=""` means exactly "not this scope". ts, level, source, event and msg are never absent; the rest are "" or 0 when they do not apply.
The two fields that come from outside the event are injected: the clock that fills ts (never time.Now() read directly, so a test is deterministic) and the source of the run's guid (never /proc read directly in a test).
Nothing here is a second logging system. The handler is slog's, the escape is internal's oneline, and the line is additive: a reader that only knows the stdout event still works.
Index ¶
Constants ¶
const Redacted = "[redacted]"
Redacted is what replaces a secret value. It is a fixed string so a query can find the lines where something was redacted, and a test can assert the mark is there.
Variables ¶
This section is empty.
Functions ¶
func ProcessGUID ¶
func ProcessGUID() string
ProcessGUID is the production guid: the kernel's boot id, this process's pid and the process's start time. A process keeps the same guid for its whole run, so its lines group, and two runs that share a pid after a reboot still differ by the boot id. A test never calls this; it injects a fixed GUIDSource instead.
Types ¶
type Clock ¶
Clock is the writer's clock. Production passes time.Now; a test passes a fixed func so ts is deterministic.
type GUIDSource ¶
type GUIDSource func() string
GUIDSource is the source of this process run's guid. Production passes ProcessGUID; a test passes a fixed func so it never reads /proc.
type Line ¶
type Line struct {
TS string // ts: UTC RFC3339Nano from the writer's clock
Level string // level: DEBUG, INFO, WARN or ERROR
Source string // source: the tool that wrote the event
Bench string // bench: the machine, by its fleet name
Verb string // verb: the verb within the tool
Job string // job: the work item's job id
Card string // card: the work item's card id
PR int // pr: the work item's pull request number
Run string // run: the work item's run id
Slot string // slot: the work item's slot
GUID string // guid: one per process run
Event string // event: start, refuse, retry, done, or the part's own noun
Msg string // msg: one human sentence, escaped through oneline by Write
DurMS int64 // dur_ms: milliseconds from start to this event
Err string // err: the error's text, escaped through oneline by Write
}
Line is one structured event: the fixed field list of SPEC-LOGS.md Part 2. It is a value, built by New and filled by the verb, and Write renders it as one JSON line.
func New ¶
func New(clock Clock, guid GUIDSource, source string) Line
New fills the two fields that come from outside the event -- ts from the injected clock and guid from the injected source -- and defaults the level to INFO, the level of a state change that is not a warning or an error. The verb fills the rest.
func (Line) Write ¶
Write emits the event as exactly one JSON object with the spec's fifteen fields. The message and the error go through pkg/oneline first, so a newline in either cannot add a second line and the object is one line whatever the sentence holds. The writer is injected: stderr in production (a unit's stderr is the systemd journal), a buffer in a test. The handler is slog's JSON handler; the handler's own time, level and msg keys are replaced by the spec's ts, level and msg fields so the object is the spec's object and not slog's.
Every field whose content comes from outside this program -- the ids, the message and the error -- passes through Redact on the way out, so SPEC-LOGS.md Part 2's one hard rule ("a secret VALUE is never logged") is enforced by the emitter and not by review. The fixed vocabulary the program writes itself -- ts, level, source, event, guid -- is never touched, so a redaction can never rename an event out from under a query.