# 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](017-contract-versioning.md) §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: 1. **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 through `tracing::{error,warn,info,debug}` macros, and carry no subscriber in `[dependencies]` — where `tracing-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 the `log` facade. 2. **Core's dependency posture constrains the shape.** Core ([ADR-001](001-crate-split.md) split; [ADR-020](020-enqueue-opt-semantics-and-bridges.md) §4's dependency posture, serde/serde_json being the sole core deps) must not grow a facade dependency — a dep on `alkstore` is a contract-posture change and buys nothing: core has **zero** `eprintln!` 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](../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.)* 3. **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. The `eprintln!` 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](007-transactional-seam.md)'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`) gains `tracing = "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` (or `log`) dependency; zero emission sites; the contract's failure reporting stays the typed error taxonomy ([core-contract.md](../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 from `eprintln!` to the `tracing` facade in this adoption (levels: `warn` for retried/degraded operations and failed diagnostics paths, `error` for 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 in `eprintln!` 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](../../tasks/pg-fix-forwarder-diagnosability.md)) surface through the same facade — `warn` on the failed reconnect connects with attempt/backoff context, `error` on 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 `Display` shape. 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 `tracing` events. - 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 wire `RUST_LOG` at 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 the `eprintln!` 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` — `tracing` facade-only; `alktunnels`/`alksocks`' `tracing-subscriber`(env-filter) entries are `[dev-dependencies]`. - [ADR-001](001-crate-split.md) (crate split), [ADR-020](020-enqueue-opt-semantics-and-bridges.md) §4 + this ADR §Context point 2's explicit reading — core stays payload-deps-only. - [ADR-017](017-contract-versioning.md) §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](004-postgres-driver.md) — the pg forwarder's `application_name` diagnosability note, the first operator-diagnosability posture in the family. - The deciding task: `tasks/decide-log-facade-adoption.md` (sign-off recorded in §Status).