[ Ionut Dumitru ]
EngineeringOct 20, 20256 min read

Logs are for your future self at 3am

Every log line is a message to a future engineer who is tired, in a hurry, and has lost all your context.

Most engineers write logs for the moment the code is working. They sprinkle in a console.log("here"), watch it scroll past in a healthy terminal, and ship. But you never read your logs when things are fine. You read them when production is on fire, when the pager went off at 3am, and when the person staring at the screen has no memory of why this code was written or what it assumed. That person might be a teammate. More often, it's you, eight months from now, with none of today's context loaded.

So write for that person. Every log line is a small act of empathy aimed at a future engineer who is exhausted, under pressure, and one bad message away from blaming the wrong system. The question isn't "does this help me debug right now?" It's "will this still make sense to someone who knows nothing?"

Log the decision, not the location

The most common log line is also the most useless: a message that tells you the code reached a point, but nothing about why it behaved the way it did. entering processOrder tells me where you are. It doesn't tell me what processOrder decided.

At 3am, you don't need a map of which functions ran. You need to reconstruct the reasoning. Log the inputs that drove a branch, the value you compared against, and the path you took as a result.

  • Not "validating payment" but "payment declined: amount 4200 exceeds daily limit 4000 for account 88213".
  • Not "cache miss" but "cache miss for key user:88213:prefs, falling back to db".
  • Not "retrying" but "retry 3 of 5 after 502 from billing-api, backing off 800ms".

Each of those lines answers a question before the tired engineer has to ask it. The first form makes them go read the code to find out what the numbers were. The second form hands them the numbers. One of those is a kindness.

Carry the identifiers all the way through

A log line without an identifier is a sentence with no subject. "User not found" — which user? "Request failed" — which request, out of the eleven thousand that minute?

The single highest-leverage habit in observability is threading a request ID, trace ID, or correlation ID through every log line in a request's lifetime. When something breaks, you grep for one ID and the entire story assembles itself in order, across services. Without it, you're stuck eyeballing timestamps and guessing which "request failed" belongs to the angry customer in the support ticket.

A log you can't grep for a single request is a diary, not an instrument.

The same discipline applies to the shape of the line. Structured logs — key-value pairs or JSON — let you filter, count, and graph after the fact. A line you can query is worth ten lines you have to read. Free-form prose feels friendlier when you write it and betrays you when you need to ask "how many times did this happen in the last hour, grouped by account tier".

Match the level to the reader's adrenaline

Log levels are not a severity vanity scale. They are a contract about who should wake up. Treat error as a promise: this needs a human, possibly now. The fastest way to make logs useless is to log expected conditions as errors. A user typing a wrong password is not an error — it's Tuesday. Page someone for it twice and they will mute the channel, and then the real error scrolls past unread.

So spend the levels deliberately:

  • error — something broke that a human must look at. Every one should be actionable.
  • warn — degraded but surviving: a fallback fired, a retry succeeded, a deprecated path got hit.
  • info — the state changes you'd want in the timeline of a normal request.
  • debug — the firehose you switch on only when you're already hunting.

And include enough to act, but never the things that turn a log into a liability:

logger.ts

log.warn("auth fallback", {
requestId,
userId: user.id,
reason: "primary idp timeout",
// never: password, token, full card number, raw PII
});

Log what happened — never the secret that made it happen.

A log that leaks a session token is no longer a debugging aid — it's an incident of its own. The tired engineer at 3am should be able to read every line out loud on a support call without leaking anything that matters.

Write the line you'd want to find

Here's the test I apply to every log statement before it ships: imagine I've been paged, it's dark, I'm annoyed, and this exact line is the first thing I see. Does it tell me what happened, to whom, and what the system did next? Or does it just prove the code ran?

The good version costs a few more seconds when you write it and saves twenty minutes when it counts — and it counts at the worst possible time, which is the only time logs ever get read. Nobody scrolls through info logs for fun. They show up because something is wrong and the clock is running.

You will not remember today's context when you need it most. The variable names will look foreign. The clever optimization will look like a bug. The only thing carrying you across that gap is the breadcrumbs you left for a stranger who happens to share your name. Leave good ones.

#Engineering#ObservabilityShare ↗
→ / AUTHOR
Ionut Dumitru
Ionut Dumitru

Full-stack engineer and product designer. Writes about building products where the engineering and the design are the same job.

→ / NEXT
CraftOct 13, 2025
Consistency is overrated when it's lazy
← All writingionutdumitru.com