Python Logging Patterns
Structured Logging with structlog
import structlog
import logging
import sys
def configure_logging(json_logs: bool = False, log_level: str = "INFO") -> None:
shared_processors = [
structlog.contextvars.merge_contextvars,
structlog.processors.add_log_level,
structlog.processors.TimeStamper(fmt="iso"),
structlog.stdlib.add_logger_name,
]
if json_logs:
processors = shared_processors + [structlog.processors.JSONRenderer()]
else:
processors = shared_processors + [structlog.dev.ConsoleRenderer()]
structlog.configure(
processors=processors,
wrapper_class=structlog.make_filtering_bound_logger(
logging.getLevelName(log_level)
),
logger_factory=structlog.PrintLoggerFactory(file=sys.stdout),
)
# Usage
logger = structlog.get_logger(__name__)
async def create_user(data: dict) -> User:
logger.info("creating_user", email=data["email"])
try:
user = await user_repo.create(data)
logger.info("user_created", user_id=user.id, email=user.email)
return user
except DuplicateEmailError:
logger.warning("duplicate_email", email=data["email"])
raise
except Exception:
logger.exception("user_creation_failed", email=data["email"])
raise
Correlation IDs in FastAPI
from contextvars import ContextVar
import uuid
import structlog
REQUEST_ID: ContextVar[str] = ContextVar("request_id", default="")
@app.middleware("http")
async def add_correlation_id(request: Request, call_next):
request_id = request.headers.get("X-Request-ID") or str(uuid.uuid4())
REQUEST_ID.set(request_id)
structlog.contextvars.bind_contextvars(request_id=request_id)
response = await call_next(request)
response.headers["X-Request-ID"] = request_id
structlog.contextvars.clear_contextvars()
return response
Standard Library Logging (minimal setup)
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(name)s %(levelname)s %(message)s",
handlers=[logging.StreamHandler(sys.stdout)],
)
# In modules: always use module-level logger
logger = logging.getLogger(__name__)
# Log with context
logger.info("Processing order", extra={"order_id": order_id, "user_id": user_id})
# Don't use f-strings in log calls (deferred formatting)
logger.debug("User %s logged in from %s", user.id, ip_address) # Good
logger.debug(f"User {user.id} logged in from {ip_address}") # Bad: formats even if DEBUG disabled
Log Levels Guide
| Level |
Use for |
| DEBUG |
Detailed diagnostic information (disabled in production) |
| INFO |
Operational events (request received, user created) |
| WARNING |
Unexpected but handled situations (rate limit hit, retry) |
| ERROR |
Failures that need attention (DB down, 3rd party API failed) |
| CRITICAL |
System-level failures requiring immediate action |