Logging Middleware
The dcc_backend_common.fastapi_logging_middleware module correlates every log line emitted while handling a request with a single request_id, and logs the requests that went wrong.
Overview
The module provides:
add_logging_middleware(app): One-liner registration on a FastAPI appLoggingMiddleware: The underlying StarletteBaseHTTPMiddlewaresubclass- Request correlation: Binds
request_idinto the structlog contextvars, so every log line during the request carries it - Failure-only logging: Successful requests are not logged; 4xx/5xx and unhandled exceptions are
- Health-probe exclusion:
/health/*is skipped entirely
Installation
Part of the dcc-backend-common package; the FastAPI pieces need the fastapi extra:
uv add "dcc-backend-common[fastapi]"Quick Start
from contextlib import asynccontextmanager
from fastapi import FastAPI
from dcc_backend_common.logger import init_logger, get_logger
from dcc_backend_common.fastapi_logging_middleware import add_logging_middleware
@asynccontextmanager
async def lifespan(app: FastAPI):
init_logger()
yield
app = FastAPI(lifespan=lifespan)
add_logging_middleware(app)
logger = get_logger(__name__)
@app.get("/translate")
async def translate():
# This line automatically carries the same request_id as any other
# log emitted while handling this request.
logger.info("translating", target_lang="en")
return {"ok": True}Behaviour
Request ID
| Step | Behaviour |
|---|---|
| Incoming | Reads the X-Request-ID request header |
| Missing | Generates a new UUID4 |
| During the request | Bound into the structlog context (structlog.contextvars), so every log line includes request_id |
| Outgoing | Echoed back in the X-Request-ID response header |
The context is cleared at the start of each request, so IDs never leak between requests handled by the same worker.
Tracing across services
Forward the X-Request-ID header when calling downstream services to correlate a single user action across the whole stack.
What Gets Logged
| Situation | Level | Event | Fields |
|---|---|---|---|
Response status >= 400 | WARNING | request_error | method, path, status_code, duration_s |
| Unhandled exception | ERROR | request_failed | method, path, error, error_type, duration_s, traceback (exc_info) |
Successful response (< 400) | — | not logged | — |
Exceptions are re-raised after logging, so your error handler still shapes the response.
Records are emitted under the logger name request, so you can filter on logger: "request" in OpenSearch.
Example production output:
{
"timestamp": "2025-07-14T09:12:03.481920Z",
"logger": "request",
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"level": "warning",
"event": "request_error",
"method": "POST",
"path": "/api/translate",
"status_code": 422,
"duration_s": 0.031
}Excluded Paths
Requests whose path starts with /health are passed through untouched — no request_id binding, no logging. Kubernetes polls these every few seconds, and the health probe router already does its own deduplicated failure logging.
API Reference
add_logging_middleware(app)
Register the middleware on a FastAPI application.
from dcc_backend_common.fastapi_logging_middleware import add_logging_middleware
add_logging_middleware(app)Parameters:
app(FastAPI): The application instance
Returns: None
LoggingMiddleware
The BaseHTTPMiddleware subclass behind add_logging_middleware(). Add it directly if you need to control middleware ordering:
from dcc_backend_common.fastapi_logging_middleware import LoggingMiddleware
app.add_middleware(LoggingMiddleware)Ordering
Register it early (outermost) so the request_id is bound before other middleware logs anything.
Module Constants
| Constant | Value | Description |
|---|---|---|
REQUEST_ID_HEADER | "X-Request-ID" | Header read on the way in and echoed on the way out |
EXCLUDED_PATH_PREFIXES | ("/health",) | Path prefixes skipped entirely |
Related Documentation
- Logger — the structlog pipeline this middleware feeds
- Health Probes — the excluded endpoints and their own dedup logging
- Error Handler — shaping the response for failed requests
Source Code
The full implementation is available on GitHub: dcc_backend_common/fastapi_logging_middleware/middleware.py
