9.3 KiB
ADR-025: The log facade — adopt tracing, engine crates only
Status
Accepted (2026-10-11, Phase 1 — the family-level posture decision
that docs/reviews/004-pre-release-general-review.md §"Findings
recorded, not fixed" item 7 explicitly recorded rather than made: the
review's item 7 named the posture gap (no facade, 17 library-path
eprintln! sites carrying the diagnostics) and left the
log/tracing/eprintln! call as a family-level decision. The
class-1 amend-in-place window (ADR-017
§2) is still open — no versioned artifact exists — so the adoption
lands pre-release, before the consumer task (the pg forwarder
diagnosability fix) needs the output form. User sign-off recorded
2026-10-11: adopting tracing, this option chosen by the user in
session — the sign-off is part of this record per the deciding
task's acceptance criterion.)
Context
The workspace ships four published crates with diagnostics but no log
facade: the engine crates emit operator-visible failures through
eprintln! — unconditional, un-filterable, un-routable stderr writes
whose verbosity the consumer cannot turn down and whose presence the
consumer didn't necessarily ask for. The review verified all sites
secret-free (no DSN/password material ever printed) and recorded the
posture question rather than deciding it.
Three forces frame the decision:
- The family precedent is unanimous. All four family repos were
checked this session (2026-10-11): alkcall, alktunnels, alksocks,
and alktty each depend on
tracing = "0.1"in their library crates, emit throughtracing::{error,warn,info,debug}macros, and carry no subscriber in[dependencies]— wheretracing-subscriber(env-filter) appears at all (alktunnels, alksocks) it is a[dev-dependencies]entry, so the integration tests can mount a sink and assert on emissions; the subscriber wiring in production is the consuming binary's/app's business. Nobody in the family uses thelogfacade. - Core's dependency posture constrains the shape. Core
(ADR-001 split;
ADR-020 §4's
dependency posture, serde/serde_json being the sole core deps)
must not grow a facade dependency — a dep on
alkstoreis a contract-posture change and buys nothing: core has zeroeprintln!sites (re-grepped) — core emits nothing and needs to emit nothing; the diagnostics live in the engines. The adoption is therefore engines-only: lightest shape, contract untouched. (Amendment note: ADR-020 §4's text is about payload encoding and says nothing about logging; the constraint applied here is ADR-020 §4's dependency line read with core-contract.md — the payload serde deps are the sole core deps. This ADR states the reading explicitly so the log decision doesn't silently stretch an encoding ADR beyond its text.) - Published-crate consumer expectations. A facade that emits
nothing until the consumer installs a subscriber is the
consumer-respectful posture — silent by default, opt-in filtering
(
RUST_LOG-style env-filter at the wiring site), structured fields, zero output cost when compiled without a subscriber. Theeprintln!posture forces stderr noise on every consumer.tracing's no-subscriber overhead is a cheap disabled-event call — invisible in the seam-latency budgets (ADR-007's measured postures).
The deciding task also named the disposition question: what happens
to the existing eprintln! sites under whichever option lands.
Decision
Adopt tracing as the log facade, in the engine crates only.
Core keeps its dependency posture untouched — no facade dep on
alkstore, ever (the engines-only shape is the contract-safe one;
moving diagnostics into core would be a separate decision with its
own record anyway).
- Dependency shape (mirrors the family, verified above): each
engine crate that emits (
alkstore-sqlite,alkstore-postgres) gainstracing = "0.1"in[dependencies];tracing-subscriber(env-filter) appears only under[dev-dependencies], where a test needs to assert on emitted events. No subscriber is wired inside any library; the consuming binary/app owns that wiring (the family's uniform posture). - Core: silent. Zero
tracing(orlog) dependency; zero emission sites; the contract's failure reporting stays the typed error taxonomy (core-contract.md). This is the explicit answer to the core-dep question the deciding task required. - Semantics of an event without a subscriber: dropped — no output, no noise. Silent by default, loud on opt-in is the facade's standing guarantee and the operator-facing property this ADR pins.
- The secret-free rule is the posture of record: no DSN,
password, token, connection-credential material, or
consumer-table content in any engine event — the review's verified
property promoted to standing rule any new emission site inherits.
Diagnostics name mechanisms, error classes, backoff/attempt state,
and engine-owned identifiers (channel names, queue names, listener
application_name-class labels), never credentials. - Site disposition: migrate all. The review counted 17
library-path
eprintln!sites (verified secret-free); the re-grep at decision time finds 19 — sqlite 10, pg 9 — the delta arriving from the review's own inline fixes re-structuring the tick/lease failure arms. All 19 migrate fromeprintln!to thetracingfacade in this adoption (levels:warnfor retried/degraded operations and failed diagnostics paths,errorfor unrecoverable-loss conditions such as watcher death or the discarded poll error); message text and secret-free discipline carry over verbatim-class. No site is kept ineprintln!and none is deleted: every emission the review verified carries operator value the facade preserves. The pg forwarder's swallowed connect/poll errors (the review's item 7 arms, the deciding task's consumer) surface through the same facade —warnon the failed reconnect connects with attempt/backoff context,erroron the loop-exit poll error. - Levels and fields are engine-local detail, not contract: the
contract's only log-adjacent pin is the error taxonomy's
Displayshape. Event field names may change without an ADR; removing an engine's diagnostics entirely would be a posture change (record one).
Consequences
Positive
- The family posture is uniform: one facade across alkcall, alktunnels, alksocks, alktty, and alkstore's engines; operators learn one wiring recipe (env-filter subscriber) per family binary.
- Consumers of the published crates get silence by default and opt-in structured diagnostics — the published-crate expectation the deciding task named is answered in the consumer-respectful direction.
- The diagnosability consumer task has its output form pinned
before it runs; the pg forwarder fixes land as
tracingevents. - The secret-free discipline is now stated law, not inherited hygiene re-verified per review.
Negative
- Two engine crates gain a dependency (the facade only — no
subscriber dep in
[dependencies]); the dep-graph delta is one crate, family-adopted four times over. - Events are invisible until the consumer wires a subscriber —
an operator running a bare integration sees no diagnostics
where the old
eprintln!posture printed unconditionally. The deployment matrix states this honestly (operators wireRUST_LOGat the consuming binary; the engine crates' docs say so). - No-op-subscriber sites still cost a disabled-event check per
emission — unmeasured here, but the family carries it and the
POC-measured budgets are
Event-free paths.
References
docs/reviews/004-pre-release-general-review.md§"Findings recorded, not fixed" item 7 — the finding (the no-facade posture, the secret-free verification of theeprintln!set, the family-level framing); the §"What was checked and clean" security sweep is the secret-free verification of record.- The family precedent (verified in-session, 2026-10-11):
alkcall/Cargo.toml:31,alktunnels/Cargo.toml:32,alksocks/Cargo.toml:40,alktty/Cargo.toml:44—tracingfacade-only;alktunnels/alksocks'tracing-subscriber(env-filter) entries are[dev-dependencies]. - ADR-001 (crate split), ADR-020 §4 + this ADR §Context point 2's explicit reading — core stays payload-deps-only.
- ADR-017 §2 — the class-1 window this adoption rides; no contract text moves (no trait, type, or error variant changes; zero consumer-inventory rows required — nothing here assumes a consumer need, the diagnostics belong to engines and operators).
docs/architecture/deployment.md— the operator-facing posture section this ADR's landing adds.- ADR-004 — the pg forwarder's
application_namediagnosability note, the first operator-diagnosability posture in the family. - The deciding task:
tasks/decide-log-facade-adoption.md(sign-off recorded in §Status).