183 lines
9.3 KiB
Markdown
183 lines
9.3 KiB
Markdown
# 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). |