Advanced·12 min·advanced · logging · observability
Logging in production
Logging — the print() replacement worth learning
Every backend job you'll ever hold uses logging, not print. Reasons that add up:
- Levels — turn debug on locally, keep it off in prod.
- Configurable per-module — quiet a noisy library without touching call sites.
- Structured — JSON logs feed straight into Grafana / Datadog / Loki.
- Includes tracebacks —
log.exception()captures the stack for free.
The five levels (memorize the order)
DEBUG— verbose inspection while developing.INFO— normal events worth remembering (user signed up,payment ok).WARNING— retryable or degraded state (gateway timeout, retrying).ERROR— the operation failed, but the app still runs.CRITICAL— the app can't continue (DB down,kernel OOM).
Set the threshold once via basicConfig(level=...). In prod, most services run at INFO.
Lazy formatting — the one perf rule
log.debug("payload=%s", payload) # RIGHT — string built only if DEBUG is on
log.debug(f"payload={payload}") # WRONG — string built every call
Matters when you leave debug calls in hot paths.
Handlers = where logs go
Default: stderr. Real apps chain multiple handlers:
StreamHandler→ stdout (for Docker, Kubernetes to pick up).FileHandler→ a file (withRotatingFileHandlerfor size caps).- Third-party: Sentry, Datadog, CloudWatch — all plug in the same way.
Structured (JSON) logs
Modern log aggregators want machine-readable input. Instead of "charge ok user=alice amount=199" write {"event": "charge_ok", "user": "alice", "amount": 199}. You can grep, filter, aggregate by fields.
Libraries that do this for you: structlog (great), loguru (nice API, some pin against it), or roll your own JsonFormatter like the starter code shows.
Don't log secrets
Password, API keys, JWT tokens, PII — never. Add a redactor filter or an allowlist for the fields you log. This bites everyone once.
Try it
- Add a second logger
"gateway"and log two INFO events under it — noticegetLogger("payments")vsgetLogger("gateway")in output. - Wrap a divide by zero, log at ERROR with
log.exception, then read the traceback in the output.