Skip to content

Add request-scoped UUID logging middleware + unit tests #9

@mattmorgis

Description

@mattmorgis

Motivation

The previous issues (#6/#7) were closed in favor of a single, consolidated task.
We need production-grade logging with per-request correlation and automated
verification in one PR.

Goals

  1. Generate a UUIDv4 per HTTP request and propagate it via:
    X-Request-ID response header
    • a contextvars.ContextVar so any log line can include it
  2. Emit a single access-log entry per request (method, path, status, latency)
  3. Enrich every log record with the request ID using a logging.Filter
  4. Provide full unit-test coverage for the above behaviour
  5. Use only the Python stdlib + FastAPI (no new deps)

Implementation Plan

  1. New module app/logging_config.py
    a. _request_id_ctx: ContextVar[str|None] = ContextVar("request_id", default=None)
    b. class RequestIdFilter(logging.Filter) sets record.request_id
    c. configure_logging() calls logging.basicConfig(...) with format:
    %(asctime)s [%(levelname)s] [%(request_id)s] %(name)s: %(message)s
    and attaches the filter to the root logger.
    d. add_request_id_middleware(app: FastAPI) -> None — middleware that:
    • creates uuid.uuid4().hex per request
    • stores in ctx-var, records start time
    • awaits call_next
    • logs access line via logging.getLogger("app.access")
    • resets ctx-var and sets X-Request-ID header

  2. Modify app/main.py
    • Import and call configure_logging() before FastAPI(...)
    • Call add_request_id_middleware(app) right after app creation
    • Remove old logging.basicConfig() block

  3. Unit tests tests/test_request_id_logging.py

    def test_request_id_header(client):
        r = client.get("/health")
        rid = r.headers.get("X-Request-ID")
        assert rid and len(rid) == 32 and int(rid, 16)
    
    def test_request_id_in_logs(client, caplog):
        caplog.set_level("INFO")
        r = client.get("/health")
        rid = r.headers["X-Request-ID"]
        assert any(getattr(rec, "request_id", None) == rid for rec in caplog.records)

    • Re-use existing client fixture from tests/test_main.py.

  4. Docs (tiny)
    • Update README.md within the same PR: add a "Logging & Request IDs"
    subsection explaining header + log format.

Acceptance Criteria

  • uvicorn app.main:app runs; logs show request IDs
  • Every response has an X-Request-ID header
  • pytest passes; new tests succeed; coverage ≥ current baseline
  • No new runtime dependencies

Files touched / added

app/logging_config.py (NEW)
app/main.py (EDIT)
tests/test_request_id_logging.py (NEW)
README.md (EDIT)

Labels

enhancement, logging

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions