ADR-0006: Body logging is a mode per direction, gated by the outcome¶
- Status: accepted
- Date: 2026-09-03
- Context:
log-request-body/log-response-bodywere booleans:truemeant every body of every exchange. That is the switch that decides the log VOLUME, and it had only two positions - off, or everything. What is nearly always wanted in practice is "bodies only when something went wrong": that reduces the volume by orders of magnitude and hits exactly the exchanges a body is wanted for. The structure for it already existed - the outcome is final before the line is written - only the third position was missing. The catch is the request body: it flows before the outcome is known, so it has to be captured and, in the common case, thrown away. That costs the capture but saves the output, and the output is what burdens the log pipeline (ELK, the shippers, the indices).
Decision¶
Each body direction has a mode, never | on-failure | always, not a
switch:
never(the default) captures nothing for logging. A size meter may still install a count-only capture, exactly as before.on-failurecaptures the body on every exchange - bounded bymax-body-bytes, likealways- and writes it to the line only whenendpoint_outcomeis notsuccess:failure,timeout, and on the reactive twincancelled, or when the status is a 4xx. The emitter decides when the outcome is final; the request side captures ahead and discards.alwayscaptures and logs on every exchange - the formertrue.- The gate is wider than the outcome vocabulary by exactly one status
class: a 4xx response keeps its
successoutcome (the application answered; the client's request was wrong) - levels, metrics and dashboards are untouched - but its bodies are logged, because a client's error is exactly the case a body explains. A 5xx is afailureand logs as well. A slow but healthy exchange stayssuccessand logs no bodies. Amended the same day: the first cut followed the vocabulary strictly and withheld 4xx bodies; that hid validation errors, the bodies most often wanted. - The former booleans are refused at binding time (
trueis not a mode name): an operator who believed body logging on must see the migration at startup, not discover a silentneverin production.
The mode lives in the shared core (BodyLogMode), so both twins and the
sibling project legatium, where the mode was designed in first and whose
namespace mirrors this one, gate the same way.
Consequences¶
- Body logging becomes affordable outside a debug session:
on-failurelogs the bodies that explain an incident and nothing else. on-failurecosts the request-body capture on every exchange (memory up tomax-body-bytesper in-flight exchange, and on the reactive twin the tee's transient copy per buffer), whether or not the line ends up with a body.measure-*-body-sizeis unchanged: it still measures what flowed, in every mode.- Breaking configuration and source change:
true/falsebecomealways/never; the properties' type changes fromBooleantoBodyLogMode. The reference configurations, the lockstep tests and both twins' guides carry the new vocabulary.