Log an attempt
Structured log levels require v0.1.7 or newer. The existing plain-text
Log / ctx.log
API remains supported.*slog.Logger with debug, info, warn, and error enabled. It supports
With, WithGroup, LogAttrs, and the context variants. Named groups become dotted field
keys, for example log.WithGroup("request").Info("Fetched", "status", 200) stores
request.status. Avoid literal dots in keys when using groups, because they share that
namespace. Custom slog levels map to the nearest lower standard severity, clamped to debug
or error. Outside a job, this logger discards records; it does not fall back to global logs.
Rust uses an explicit .emit() builder. Dropping the builder without emitting does not
record anything. Neither logger installs a global handler or captures unrelated application
logs. Automatic tracing capture is not provided by this change.
When logs become visible
The console shows saved entries under the job’s attempt timeline, with a level filter, capture timestamp, message, and expandable fields. Existing strings appear at info level without an invented timestamp. A structured timestamp is captured on the worker clock; it is diagnostic information, not the store clock used for leases and scheduling. Logs are buffered in memory and saved with a successful fenced acknowledgement for success, retry, skip, and undecodable. They are not live-streamed. A process crash or lost lease before acknowledgement can lose the buffer. Snooze, rate-limited, and revoke outcomes do not retain logs. Successful jobs need positiveretention_ms to remain
inspectable; zero deletes the successful record and its logs. Transactional Once
completion does not flush the outer attempt’s buffer, so use application logging for
diagnostics that must survive that path.
Bounds and privacy
- Each attempt accepts 100 entries, followed by one visible cap marker. Plain and structured calls share this budget. Calls made after the runtime takes the buffer are discarded.
- Each structured entry is at most 2 KiB including its encoded fields and metadata. Oversized entries remove fields before shortening the message and display Truncated.
- Up to 32 fields are captured. Keys are capped at 128 UTF-8 bytes and string values at
1 KiB. Fields are JSON scalars: strings, numbers, booleans, and null. Arbitrary objects
and arrays become
[unsupported log value]; log selected scalar properties instead. - Go resolves
slog.LogValuer, formats errors as text, and formats duration/time attributes as strings. Nested slog groups are bounded to four levels. Invalid UTF-8 is repaired. - Job log fields are not automatically redacted or encrypted. Do not log tokens, passwords, full payloads, or sensitive customer data. Encrypted job payloads do not encrypt logs.
Storage compatibility
The attempt’slogs property remains a string array. New structured strings begin with
the record-separator character U+001E followed by headgate-log-v1: and a JSON object
containing level, at_ms, message, optional fields, and optional truncated.
The shared Go headgateshared.DecodeLog and Rust headgate::LogEntry::decode readers
understand this format. Unknown versions and malformed entries remain literal info text.
The console uses the same versioned format; an older console displays the encoded text.
No schema migration or replacement of existing attempt history is required.
Run locally
The progress examples emit info, debug, and warning records from a real handler. From the repository root, using the development checkout:/jobs/job-running-1042. Its earlier retry attempt includes all four levels.
Report progress
Persist a result in Rust
record_result stages bytes on the job context. A successful acknowledgement commits the
result and job completion atomically.
Report and inspect progress in Go
Progress writes are fence-verified. A worker that has lost its lease cannot overwrite
progress reported by the job’s current attempt.