Most logging advice assumes the bug is yours. Under that assumption a log line is a note to yourself: enough to jog your memory about code you wrote and can re-read at will.
Integration work breaks that assumption. When a booking behaves strangely, the defect is often in a system I have no access to — a supplier's platform, a partner's middleware, something behind an endpoint and a PDF. I cannot read that code or add a print statement to it. What I can do is show what crossed the boundary, and for that to settle anything it has to convince a reader who does not work here and starts from the assumption that the problem is mine.
So the design goal changes. The log is not an aid to my memory. It is evidence, and it will occasionally be attached to a message to another company. Everything below follows from that. (Reading these records is a different discipline and another post; this one is about what has to be in them.)
As it left, not as you intended
The first rule is the one most implementations get wrong: capture the request as it actually went out, not the object you assembled.
Those are different artifacts, and they differ exactly where it matters. An HTTP client adds headers, an interceptor injects a token, a serialiser drops keys and formats a date its own way, a retry wrapper rewrites something on the second attempt. Log the object you built and you have logged your intention — a faithful record of what you believed you were sending, and worthless in an argument about what the other side received.
So the capture point is the last one before the bytes leave: the serialised body, the final header set, the resolved URL. And it must sit where no later code can mutate it. A record that some middleware can still touch after it is written is not evidence, because the first question a careful reader asks is whether the log reflects the call or the code's opinion of the call.
Keep the body even when the status was 200
Some suppliers answer HTTP 200 with an error inside the body. That is normal in this trade, and it destroys the most common logging shortcut there is: keep responses for failures, drop them on success. Under that policy the one record you needed — the successful-looking call carrying a rejection — is the one you discarded, and you find out days later when someone asks why a booking never confirmed.
The status code is a transport opinion. In integration logging it is a field in the record, not a filter on whether the record exists. Keep the response body, on every call, at whatever retention you can afford — and if you must sample, sample by traffic, never by outcome.
A record that can be re-fired
The property that makes a log entry stop being an argument is reproducibility. Our provider logs are kept complete enough that a failed call can be re-fired as the exact request that was sent, rebuilt as a curl.
The effect on a conversation is out of proportion to the effort. "We believe we sent a valid request" invites a discussion about beliefs. "Here is the request; run it and you will see what we saw" ends it — either it reproduces, and the ball is on their side, or it does not, and something about the environment differs, which is now the finding. Both beat a thread comparing recollections. A claim you can replay is a claim you can test.
Let the schema decide which fields are recorded
When a record summarises the fields of a request, the field list should come from the endpoint's own validation schema rather than be hand-picked. The schema is the complete statement of what the endpoint accepts; a hand-written list is a snapshot of what somebody thought mattered that day, and it goes stale on the next release without anyone noticing.
Deriving it also removes a maintenance trap: one list, in one place, moving when the contract moves. A hand-kept second list will eventually omit the field that turns out to be the whole problem, and omit it silently.
Absent, empty and null are three different facts
A field that was never sent, a field sent blank, and a field sent explicitly null mean three different things, and often produce three different behaviours downstream: one falls through to a default, one overrides it, one is rejected outright.
The important word is preserve, and it belongs at capture time rather than at reading time. Most serialisers erase this distinction for free — a key with an undefined value simply vanishes on the way to JSON — so by the time anyone looks at the record, the evidence that separates the three cases is already gone. That is a capture decision disguised as a formatting detail, and it has to be made deliberately, once, in the code that writes the record.
Three constraints, or the evidence becomes the incident
Everything above pushes toward recording more. Three things push back, and they are not negotiable.
Credentials never enter the record. Tokens, passwords, API keys and card data are stripped at the point of capture — not masked on display, not filtered by the viewer. A record that ever held a secret has held it, and the whole point of these records is that they get zipped into folders and sent to other companies.
Volume has to be bounded. A full request and response for every supplier call is, at search volume across dozens of integrations, one of the largest write streams in the system. So it lives behind a flag, with retention, and a deliberate decision about which environments run it wide open. I have no figure for what a day of full provider logging costs in storage — nobody has priced it in a way I could quote — but the shape of that number is why the flag exists.
The logging path must fail soft. If the log write throws, times out or fills a disk, the request it describes must still complete. A service that falls over because it could not write a log has converted an observability feature into an outage — and it will do that when traffic is highest, which is also when the logs would have been most useful.
The rule
Log for the reader who cannot read your code. That is the whole design: what left the process, what came back, verbatim, replayable, secrets removed, distinctions intact. Everything you keep is something you will not have to argue about later. Everything you summarised away is something you will one day be asked to prove.