Documentation / Internal package reference
Audience: framework composition and integration maintainers.
Status: implemented synchronous internal JSON profile, with a separately owned
public Adapter; no production certification.
Package: github.com/frost-leo/fathomry/internal/logging/zerolog/v1.
- Responsibilities
- Capabilities and call sequence
- Structured records and context
- Results, failures and ownership
- Resource and compatibility limits
- Executable evidence and provenance
This independently selectable integration uses zerolog v1.35.1. It imports
neither Zap nor a companion rotation engine and introduces no grouping-level
logging interface. SDK major, OptionsV1 format 1, preparation revision and actual
consuming-build version are separate axes.
It supports explicit byte sinks, structured-record sinks and owned local files; multiple sinks receive one logical event. File output owns the rotation, compression, retention, recovery and filesystem contract. No CLI, application logger selection, exporter/backend, durable execution ledger or Temporal Workflow command/serialization is implemented here.
- Inert
PrepareV1(OptionsV1, layers...)validates/freezes source settings throughresource.Prepare. It performs no file I/O and discovers no environment, default stdout/stderr, config files or log directories. - Inspect final Prepared.Metadata, then Prepared.Select with exact BindingsV1.
Kind declarations are data; writer/record/managed-record handles are separate.
Legacy Select extracts its original live bindings and preserves prior overlay
removal/error semantics. Composition adds final
resource.WithLimitsand callsresource.Assemble. Construction opens only selected owned files. A failed assembly cannot bind; retain its returned cleanup responsibility and report. - Create an independent
invocation.Inbox[Result], then callBind. The boundLoggerexposesLog,Sync,Rotateand a safeProfile, not the native logger, owning handles, writer replacement or mutable hooks. - Call with a caller-owned context and explicit
fault.Correlation. Inspect both setup error and the accepted receipt'sResult.Err(). The inbox receives that same result independently, even if the caller ignores it. - The framework handles the inbox evidence before releasing its delivery slots. Close the owning/borrowing assembly, not the logger. Closing a borrowed scope only returns that scope's responsibility. Already established borrowing scopes may finish after the original owner's shutdown starts.
The consuming executable runs this complete local sequence, decodes both gzip/active output and a second sink, and inspects actual build facts. It is an internal maintainer fixture, not business-owned startup boilerplate or a public API example.
Bootstrap zero values select the documented defaults. Explicit overlays retain the shared configuration rules: structural validation of every layer, fixed precedence, recursive object merging, list replacement and rejection of unknown fields. Zero/null after overlay is not an instruction to reapply defaults.
Before hashing runtime sink names or serializing defaults, Select bounds the
aggregate supplied settings-string bytes to 1 MiB. This is a necessary byte check,
not early semantic validation: an invalid default that an authorized overlay
repairs is still allowed. Shared preparation independently enforces the 1 MiB
encoded-default/document limits, including JSON expansion.
| Setting | Default and supported range |
|---|---|
Name |
Required non-secret source identity; shared resource label rules |
MinLevel |
Info; Trace, Debug, Info, Warn, Error, Fatal, Panic |
MaxRecordBytes |
16 KiB; 1 KiB–1 MiB |
Timeout |
5 s; 1 ms–1 min; overlays use timeout_ns |
QueuedCalls |
0, immediate overload refusal; 0–64 |
Sinks |
1–8 unique non-secret names; each selects exactly one output |
Sink MinLevel |
Trace; source and sink thresholds both apply |
Caller |
false; opt-in bounded original file/function/frame information |
Writer is an explicit borrowed io.Writer, including caller-selected stdout
or stderr. Records is an explicit borrowed RecordWriter. File selects an
owned file sink. No runtime handle enters the prepared schema.
Overlays may select/configure files, but cannot invent, rename or change the kind
of a runtime-bound sink. Replacing the sink list requires complete entries;
unselected handles never run.
New inert kinds include explicit managed-record, bound through BindingsV1.ManagedRecords. It alone selects the typed independent-event policy; legacy RecordWriter and byte/file failure-stop remain unchanged. Prepared.Select requires exactly the selected names/kinds and copies the binding maps before use.
LimitsV1 derives recommended admission from bootstrap options, before
overlays. A changed effective bound needs corresponding limits. Bind
requires exactly one active root, sufficient bytes and a queue no larger than the
effective setting; aliases cannot reset the original allowance.
EvidenceBytesV1 supplies the per-result declared inbox reservation.
Prepared.Metadata is authoritative after overlays. It also reserves physical residence, logical policy views, cumulative derivations and logical file content. PolicyBytes pads all severity spellings to the maximum admitted width so later level-only adoption fits the original envelope. WithPolicy shares the exact original Access, queue, output/file/failure state and dependencies. Only source and sink thresholds may change; PolicyDescription differs from physical-source identity. Public composition bounds live policies and releases the physical owner only after its last logical view. There is no new allowance or unlock/reopen trick.
Log accepts a closed slog.Attr vocabulary rather than arbitrary SDK events:
- UTF-8 string, bool, signed/unsigned 64-bit integer and finite float64;
- duration as integer nanoseconds; time as UTC RFC3339Nano, years 1–9999;
- null through
slog.Any(key, nil); - canonical nested slog groups and top-level empty groups. Dotted keys remain literal keys.
Native slog constructors remove empty child groups; their group values are
immutable. A noncanonical empty child inserted by changing native group storage
is explicitly refused with ErrUnsupported before admission, not silently
discarded during copying. Top-level empty groups remain {} in the output.
Native group contract.
There are at most 64 attributes including groups, 8 levels, and 128 UTF-8 bytes per nonempty key. Duplicate keys in the same group are rejected. Closed Value adds null, binary/base64, UTF-8 byte-string, float32, arrays and maps through exact slog.Any Value carriers. Nodes are capped at 256; key/depth/record bounds still apply. Present empty collections differ from null. Non-finite values refuse. Unselected members must be empty, including inactive time/location state. Arbitrary Any/byte slices/errors, LogValuer, marshalers, caller raw JSON and native callbacks remain refused. Only bounded library-generated fragments reach native RawJSON; the user supplies no raw fragment or formatting callback.
Input accounting charges message/string-value/key bytes plus 32 bytes per
attribute. Both that total and the complete encoded record including newline
and fixed metadata must fit MaxRecordBytes. JSON escaping can therefore
cause an accepted invocation to report an encoding-limit failure before any sink
is attempted. Oversized message/string values are rejected by byte length before
UTF-8 scanning, including when an oversized value is also malformed. Data is
never truncated to fit.
Legacy scalar input keeps its exact 32-byte attribute charge. New closed values add their declared node storage. Retained With separately accounts for actual attribute headers without shrinking the historical scalar domain: 128 cumulative views and 8 MiB per physical source, shared by every policy alias. Reservation and copy are serialized against physical close; a closed or exhausted owner refuses before copying payloads. Maximum view storage is MaxRecordBytes+768; these are declared envelopes, not hard RSS.
LogEntry accepts original time/return-PC and resolves the actual frame with CallersFrames when Caller is selected. Zero Time/PC are absent. Record.PC keeps the original return PC separately from the resolved Caller. Closed float32/64 encoding preserves default finite numeric fidelity without consulting a mutable native precision callback; native global Disabled still refuses event creation.
JSON has fixed level, message, resource, correlation and attributes
fields; time is omitted for an absent timestamp and caller is opt-in.
The resource object contains provider, original scope,
source and preparation revision, never file paths/settings. Correlation preserves
call, parent and opaque owner; it is not authentication or inferred Run/Item state.
User fields remain inside attributes and cannot override these fixed fields.
Arguments are borrowed without concurrent mutation until Log returns.
Structured sinks receive an immutable Record with copied attributes and
read-only shared backing. AttributesCopy returns independent attribute/slice
storage, but native slog group values retain their immutable contract. Replace
attributes using native constructors when editing a copy; do not modify a
group's backing slice. Byte and source-metadata copies remain independently mutable.
Time values lose their monotonic component and normalize to UTC. Formatting and
runtime JSON guards are diagnostic redaction, not the explicit JSONCopy data
inspection API.
The operation context, including caller values, reaches RecordWriter; it is
not stored in the record or result. An adapter can inspect authorized trace
association and all typed attributes without reconstructing a pooled native
event. That adapter's export, buffering, retries and retained-record budgets remain
explicit separate responsibilities. The implemented
OpenTelemetry RecordWriter bridge
uses this boundary without changing borrowed ownership or local outputs.
The public boundary supplies the separately owned restricted shared slog gateway,
not the selected native Handler or unrestricted standard compatibility.
Each selected sink has separate copied SinkResult evidence:
Filtered: selected thresholds excluded this event, not attempted delivery.Attempted: actual byte/structured write entry.Acceptedrequires full bytes with nil error, or a nil structured-sink acknowledgement.Writtenis a byte writer's reported count.BytesKnowndistinguishes a valid count from absent or malformed evidence; neither proves durability.RotatedandSyncedconfirm successful local maintenance only.WriteErrorandMaintenanceErrorretain original causes separately.Stoppeddistinguishes latched destination state from one rejected event.
A failed invocation may have known accepted sinks and untouched or uncertain
others. An error does not short-circuit later eligible sinks, but context expiry
prevents their later entry. Legacy byte/record and failed file outputs stop until
explicit reconstruction/recovery. Only a managed-record RecordRejected outcome
permits the next independent record; it keeps the original event failed and does
not replay it. RecordAccepted requires nil error, Rejected/Stopped require a
non-nil/non-typed-nil error. Malformed outcomes and panics stop conservatively.
Closed/disabled/unknown state remains stopped; no automatic retry or stream repair.
A maintenance failure can leave archive effects even when Rotated is false.
Shared invocation.Attempts remains unobserved (Observed=0, Exact=false)
for Log, Sync and Rotate. This integration does not instrument a shared
SDK-attempt count or impose a native-attempt maximum. A borrowed sink may queue
without executing an SDK, or synchronously retry inside one call; counting its
entry would establish neither an exact count nor necessarily a lower bound.
Per-sink Attempted, Accepted, Written, Synced and Rotated retain the
separate known output facts. Encoding, wrapper entries and filesystem syscalls
are not substituted for the shared SDK-attempt contract. Borrowed sink owners
must bound their own native retries and resource use.
Shared fault.Error presentation excludes record data and native text while
preserving errors.Is/As. Deliberate native-cause inspection is not redacted.
An injected sink panic is contained without retaining its arbitrary panic value;
the attempted effect remains unconfirmed, later sinks continue, and synchronous
ownership/evidence completes. Sinks must not terminate the goroutine/process or
hide resource use in workers. They must bound errors and any retained records.
Borrowed handles are never flushed or closed, including when they implement
io.Closer. Their owners arrange dependency order, synchronization outside this
source, flush and release. Sync and Rotate operate only on owned files and
refuse a source with no file outputs. There is no multi-sink atomicity or rollback.
One admitted root serializes output; a slow sink couples later sinks. Admission waits have bounded count/declared bytes. There is no worker, retry, timer, asynchronous log queue or lossy diode writer. Context deadlines cannot interrupt an uncooperative byte writer or an in-flight kernel file operation. Such work retains its lease and evidence reservation until it actually returns; assembly close reports incomplete rather than releasing early.
If the final/only writer returns a full successful acknowledgement after the context was canceled, that acceptance remains recorded and is not retroactively changed into failure. Cancellation before later sink entry is recorded separately; it does not undo earlier effects.
Per-root declared accounting is 2 MiB + 24 * MaxRecordBytes, including a
sequential gzip workspace and bounded encoding/copies. Per-result accounting is
64 KiB. Neither bounds RSS, Go's allocator/GC, zerolog's process-global event pool,
arbitrary native cause graphs, or caller-retained record copies. Optional Observer
loss cannot replace or block the independent inbox. Required evidence is not a
durable execution ledger.
No zerolog global is mutated. Explicit field encoding avoids global field names,
formatters, error callbacks and severity actions. Native global Disabled
(or a higher custom override) still prevents native event creation: construction
or an enabled Log reports ErrUnsupported, never false delivery. The
binary_log build is likewise rejected before opening files. Filters and
maintenance do not claim an emitted event.
Profile copies safe effective settings, omitting sink names, paths and private
content. It does not characterize arbitrary injected sink implementations,
deployed filesystems or exporters. Actual binary inspection and
compatibility.Assess remain separate; missing records/unknown facts do not
become support. There are no power-loss, distributed quota, cross-process shared
file writing, production export, broad performance or multi-platform guarantees.
- Record tests: bounded types/escaping, hostile callbacks, globals, fuzzing.
- Logging tests: multi-sink attribution, immutable context, saturation, cancellation, aliases, concurrent complete records and independent evidence.
- File tests: independently decoded output, constant-timestamp collisions, fault containment, exclusive ownership, recovery refusal and cleanup.
- Integration tests: restricted facade, actual consuming binary/version/imports, JSON-build refusal and compatibility evidence boundaries.
This applies the existing integration standard S01–S11 at
3baf854adc270609c2aef9f6f67debb5b0efea0a; it changes no shared mechanism or
Workflow contract. Issue #31
records the scope and native counterexamples, not an acceptance certificate.
Pinned native behavior: zerolog logger,
event finalization.