Context
Our current logging is a single global basicConfig() call that prints naïve human-readable lines. For production-ish usage (even for a hobby project) we need:
- A unique request ID so we can correlate all log entries that belong to the same HTTP request.
- A single access-log line per request that captures method, path, status and latency.
- The request ID returned to the caller via an
X-Request-ID header so they can quote it in bug reports.
- No new external dependencies (project requirement) – stick to stdlib + FastAPI.
Task
Create a new module app/logging_config.py that provides both logging configuration and a FastAPI middleware for request IDs. Then wire it into app/main.py.
Implementation details
-
Context var & filter
• Create a contextvars.ContextVar named _request_id_ctx that stores a str | None.
• Implement class RequestIdFilter(logging.Filter); filter() should set record.request_id to the current value or "-".
-
configure_logging()
• Call logging.basicConfig() with:
level=logging.INFO,
format="%(asctime)s [%(levelname)s] [%(request_id)s] %(name)s: %(message)s",
• Register the RequestIdFilter on the root logger (logging.getLogger().addFilter(...)).
-
Middleware
def add_request_id_middleware(app: FastAPI) -> None:
@app.middleware("http")
async def _request_id_middleware(request: Request, call_next):
import uuid, time, logging
req_id = uuid.uuid4().hex
token = _request_id_ctx.set(req_id)
start = time.perf_counter()
try:
response = await call_next(request)
finally:
duration_ms = int((time.perf_counter() - start) * 1000)
logging.getLogger("app.access").info(
"%s %s %s (%d ms)",
request.method,
request.url.path,
response.status_code if "response" in locals() else "ERR",
duration_ms,
)
_request_id_ctx.reset(token)
response.headers["X-Request-ID"] = req_id
return response
• Expose this helper; consumers call add_request_id_middleware(app) once.
-
Integrate in app/main.py
• Before creating the FastAPI app or as soon as possible, call configure_logging().
• After app = FastAPI(...), call add_request_id_middleware(app).
• Remove the old logging.basicConfig(...) call.
• Ensure all existing log lines remain intact – the filter will enrich them automatically.
-
Compatibility
• No other files should need changes.
Acceptance criteria
uvicorn app.main:app starts without errors.
- Each HTTP response contains a header
X-Request-ID whose value is a 32-char lowercase hex UUID.
- All log lines include the same request ID in square brackets for that request.
- Access log line format example:
2025-07-08 12:34:56,789 [INFO] [d68a9d02c4c5493594f9654a4b8ac0fe] app.access: GET /health 200 (3 ms)
- Unit tests will be added in a follow-up issue.
Notes
This issue introduces a new file and modifies only app/main.py, minimizing conflict risk.
Context
Our current logging is a single global
basicConfig()call that prints naïve human-readable lines. For production-ish usage (even for a hobby project) we need:X-Request-IDheader so they can quote it in bug reports.Task
Create a new module
app/logging_config.pythat provides both logging configuration and a FastAPI middleware for request IDs. Then wire it intoapp/main.py.Implementation details
Context var & filter
• Create a
contextvars.ContextVarnamed_request_id_ctxthat stores astr | None.• Implement
class RequestIdFilter(logging.Filter);filter()should setrecord.request_idto the current value or"-".configure_logging()
• Call
logging.basicConfig()with:• Register the
RequestIdFilteron the root logger (logging.getLogger().addFilter(...)).Middleware
• Expose this helper; consumers call
add_request_id_middleware(app)once.Integrate in
app/main.py• Before creating the FastAPI app or as soon as possible, call
configure_logging().• After
app = FastAPI(...), calladd_request_id_middleware(app).• Remove the old
logging.basicConfig(...)call.• Ensure all existing log lines remain intact – the filter will enrich them automatically.
Compatibility
• No other files should need changes.
Acceptance criteria
uvicorn app.main:appstarts without errors.X-Request-IDwhose value is a 32-char lowercase hex UUID.2025-07-08 12:34:56,789 [INFO] [d68a9d02c4c5493594f9654a4b8ac0fe] app.access: GET /health 200 (3 ms)Notes
This issue introduces a new file and modifies only
app/main.py, minimizing conflict risk.