Production Checklist — Logging, Monitoring & Hardening
AdvancedThe launch gate: structured JSON logs with request IDs, error tracking (Sentry), metrics + alerts on the RED trio, security headers and rate limits, timeouts on everything external — and the checklist that ties all 40 chapters together.
Overview
Everything before this chapter made the service work; this one makes it operable at 2 a.m. Logging becomes structured JSON — one line per request, request_id everywhere (the middleware you built), so an incident is a query, not an archaeology dig. Errors flow to a tracker (Sentry's FastAPI integration is two lines) that groups, alerts, and carries the request context. Monitoring watches the RED trio — rate, errors, latency percentiles (p99, not averages) — with alerts on symptoms users feel, not on CPU graphs. Hardening is the accumulated discipline of the whole track: docs off, CORS strict, rate limits on auth routes, security headers, timeouts on every external call so one slow upstream cannot exhaust your workers. None of it is new machinery — it is the chapters you already built, assembled into a launch gate.
Structured Logs + Error Tracking + Metrics
JSON logs make grep-by-request-id trivial and feed log platforms directly; Sentry catches what the catch-all handler logs; /metrics exposes the numbers dashboards and alerts consume.
# ── 1. Structured JSON logging ──
import json, logging, sys
class JsonFormatter(logging.Formatter):
def format(self, record):
entry = {"ts": self.formatTime(record), "level": record.levelname,
"logger": record.name, "msg": record.getMessage()}
for key in ("request_id", "method", "path", "status", "ms", "user_id"):
if hasattr(record, key):
entry[key] = getattr(record, key)
return json.dumps(entry)
handler = logging.StreamHandler(sys.stdout) # stdout — the platform collects
handler.setFormatter(JsonFormatter())
logging.basicConfig(level=logging.INFO, handlers=[handler])
# access middleware logs with extra= (marries the Middleware chapter):
# log.info("request", extra={"request_id": rid, "method": m,
# "path": p, "status": s, "ms": elapsed})
# → {"ts":"...","level":"INFO","msg":"request","request_id":"7f3a",
# "method":"GET","path":"/orders/42","status":200,"ms":3.2}
# Incident flow: user reports error ref 7f3a → filter request_id=7f3a →
# the request's whole story. NEVER log: passwords, tokens, OTPs, full cards.
# ── 2. Error tracking (Sentry) ──
import sentry_sdk
sentry_sdk.init(dsn=settings.sentry_dsn, environment=settings.env,
traces_sample_rate=0.1) # + FastAPI auto-integration
# unhandled exceptions → grouped issues, stack + request context, alerts.
# Your catch-all handler (Errors chapter) and Sentry coexist: user gets
# the bland 500 + ref id; Sentry gets the truth.
# ── 3. Metrics: the RED trio ──
# pip install prometheus-fastapi-instrumentator
from prometheus_fastapi_instrumentator import Instrumentator
Instrumentator().instrument(app).expose(app) # → GET /metrics
# Rate, Errors, Duration (p50/p95/p99 histograms) per route.
# Alert on what users feel: 5xx ratio > 1% (5 min) · p99 > 2s
# · readiness failing · DB pool exhausted. Not on CPU%.Hardening + the Launch-Gate Checklist
Security headers in one middleware, rate limits where credentials are guessed, timeouts everywhere — then the checklist that reprises the entire track. Print it; gate releases on it.
# Security headers (one middleware):
@app.middleware("http")
async def security_headers(request, call_next):
resp = await call_next(request)
resp.headers.update({
"X-Content-Type-Options": "nosniff", # no MIME guessing
"X-Frame-Options": "DENY", # no clickjacking iframes
"Strict-Transport-Security": "max-age=63072000; includeSubDomains",
"Referrer-Policy": "strict-origin-when-cross-origin",
})
return resp
# ── THE LAUNCH GATE — every box is a chapter you've done ──
# Config & secrets
# □ all config via Settings/env; missing required = boot failure
# □ secrets in platform store; .env gitignored; docs_url=None in prod
# Auth
# □ Argon2/bcrypt hashes · short JWT + refresh · algorithms pinned
# □ rate limit /token and /otp/* · vague login errors (no enumeration)
# Data
# □ migrations via Alembic release step · pool sized vs max_connections
# □ pool_pre_ping · N+1 audited on list endpoints · size caps (le=100)
# Resilience
# □ timeout on EVERY external call · retries only with backoff+jitter
# and idempotency · graceful shutdown verified (SIGTERM test!)
# HTTP surface
# □ CORS explicit origins · security headers · upload caps (app + proxy)
# □ response_model everywhere (no leaks) · error envelope, no tracebacks
# Observability
# □ JSON logs w/ request_id · Sentry wired · RED alerts · /health/live
# + /health/ready wired to the platform
# Tests & deploy
# □ CI green: validation tables, authz matrix, race tests
# □ image non-root · exec-form CMD · same image staging→prod
#
# Ship it. 🚀 (Then read the Kafka and Microservices tracks —
# this service is about to have siblings.)Key Points to Remember
- 1JSON logs to stdout with request_id — incidents become log queries
- 2Sentry for grouped, alerting errors; users get ref ids, trackers get tracebacks
- 3Watch RED (rate, errors, duration p99) and alert on user-felt symptoms
- 4The launch checklist assembles the whole track: config, auth, data, resilience, observability
Interview Questions
Sign in to ask AriaA user reports "error ref 8c2f" from last night — trace your exact debugging path.
Which five alerts would you configure first for a new FastAPI service, and why those?
Design the full observability story for a payments API — logs, metrics, traces, and what pages someone at 2 a.m.
Ask Aria about Production Checklist — Logging, Monitoring & Hardening
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.