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 buildinginfo: a normal event worth recordingwarn: something off but handlederror: 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.