Skip to main content
Operate/Chapter 76 · Logging

Leaving a Trail

Share

Share this page

Pass it to someone who needs it.

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.

12.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.

12.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 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"}

12.1.3Logs 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.

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

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.

Requirements:
- Emit each log as JSON with a level (debug, info,
  warn, error), a timestamp, an event name, and
  named fields.
- Log meaningful events, caught errors, and slow
  or failed operations, each with enough context
  to trace what happened.
- NEVER log passwords, tokens, API keys, card
  numbers, or full personal data. Redact those
  fields before anything is written.
- Pick the standard structured logger for my
  stack and give me the exact setup and commands.

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.

Discussion

Questions, ideas, and feedback on this chapter.

Mahmoud Zalt

Mahmoud Zalt

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

ExploreBook a call
Companion RepoContribute
Support me

Support my work

A small tip keeps the free work coming.

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