Skip to main content
Handbook/Operate/Page 133 · Logging

Leaving a Trail

Share

Share this page

Pass it to someone who needs it.

Star on GitHub

Key takeaway: Leave a useful trail

Chapter 14 of 15 · Operate

Run and maintain it in production

Shipping is not the finish line; it is the start of running the thing. This part is how you keep it alive: see what it is doing, get told when something is wrong, and recover fast when it is.

By the end of this part, you can:

  • See what your app is doing in production
  • Get alerted the moment something goes wrong
  • Back it up so a bad day is never a lost one
  • Handle incidents calmly and watch the bill
Done:Ship·You are here: Operate·Next:Scale

Your app is shipped and running, and right now you cannot see inside it. A user hits an error, a nightly job dies, a page crawls, and you have no idea why. Scattered print statements are not a trail, they are noise you cannot search when it counts. This chapter gets you logs you can read back later and actually trust.

14.1.1Log the events, never the secrets

A log is the app narrating what it just did. Log the moments that matter: a request handled, an error caught, a payment taken. Give each one enough context to answer "what happened" later, which user, which record, what failed.

The harder discipline is what never goes in. Never write sensitive data to a log: passwords, access tokens, API keys, full card numbers, anyone's private information. Logs get copied, forwarded to other services, and read by people who should never see any of it. The OWASP logging guidance spells out the full list; the rule of thumb is log that a login happened and whose, never the password typed.

Watch out: the fastest way to leak secrets is logging a whole request or response object. Tokens and personal data ride along inside it. Log named fields, never the raw blob.

Every field faces one check before it reaches the log.

14.1.2Levels and fields make logs searchable

Give every line a log level so you can filter by how much it matters:

  • debug: detail you only want while building
  • info: a normal event worth recording
  • warn: something off but handled
  • error: something broke

In production you keep info and up, and stay quiet below it.

Then give every line the same handful of fields, so any one of them can pull the whole story back out. A correlation id is the important one: a value generated when a request arrives and stamped on every line, error and trace that request produces. Carry the user id and the running version beside it and you can filter by any of the three.

09:02 info checkout.started user=u_41 corr=a1b2 ver=1.4.2 09:02 error checkout.failed user=u_41 corr=a1b2 ver=1.4.2

One id, and everything that happened in that moment comes back together, here and in every other tool you point it at.

A line's level decides whether production keeps or drops it.

Then use structured logging: emit each log as data, a level plus named fields, instead of gluing a sentence together. A sentence you can only skim by eye. Structured lines you can search, filter, and count across millions of them.

# Before: a sentence you cannot search, leaks data print("user " + email + " logged in with " + pw) # After: structured, leveled, safe to ship log.info({ event: "auth.login", userId: 4821, ok: true }) # -> {"level":"info","event":"auth.login", # "userId":4821,"ok":true,"ts":"2026-07-10T08:14Z"}

14.1.3Log who did the dangerous things

One class of event you log even when nothing is broken: the privileged and the irreversible. Who logged in and from where, who failed to, who changed a role, who exported or deleted data, who moved money. That audit trail is how you notice a break-in yourself, instead of hearing about it from a stranger weeks later.

Two of those deserve to reach you, through the alerting the rest of this part sets up: a spike in failed logins, and any admin action at all. The rule above still holds on every one of these lines, an id and an event name, never the token or the password that came with it.

14.1.4Logs feed observability

Clean, structured logs are the raw material for observability: asking any question of your running system and getting an answer from what it already emits. Why is this slow? How often does this fail? What did this one user hit?

Random print strings answer none of that. Structured events you can search, count, and alert on, and that is the base every metric, alert, and timeline in the rest of this part sits on.

One structured logger feeds search, counts, and alerts.

Set that foundation now, one structured logger your whole app writes through:

Ready prompt
Act as a senior engineer setting up logging for my running app. Replace scattered print and console statements with one structured logger the whole app calls through. Read my data inventory, my retention policy, and my naming conventions first. Requirements: - Emit each log as JSON with a level (debug, info, warn, error), a timestamp, an event name in my naming style, and named fields. - Log meaningful events, caught errors, and slow or failed operations, each with enough context to trace what happened. - Keep an audit trail of the privileged and the irreversible: logins and failed logins, role changes, exports, deletions, money moved, each with who did it and from where. - NEVER log passwords, tokens, API keys, card numbers, or anything my data inventory marks personal. Redact those fields before anything is written. - Pick the standard structured logger for my stack, give me the exact setup and commands, and add "all logging goes through the logger" to my rules file. If you need the full reasoning behind this step, read https://zalt.me/guides/vibe-coding/operate/logging My app and what I need to see:

Do this now: paste the prompt, replace your print statements with one structured logger, and write your next log line with a level and named fields, no secrets in it.

Mahmoud Zalt

Mahmoud Zalt

Software engineer, 16+ yrs · built Sistava.com in 3 months, idea to production, using these methods

Resources
Star on GitHubContribute
Donate

Support my work

A small tip keeps the free work coming.

© 2026 Mahmoud Zalt. Free to read, not to republish.
Copyright & license