# FastAPI / ASGI Integration For a complete, production-ready example with health checks, metrics, and graceful shutdown, see the [FastAPI Production Template](https://github.com/chris-haste/fapilog/tree/main/examples/fastapi_production). ## FastAPIBuilder (Recommended) `FastAPIBuilder` provides the full power of fapilog's configuration with FastAPI-specific additions: ```python from fastapi import Depends, FastAPI from fapilog.fastapi import FastAPIBuilder, get_request_logger app = FastAPI( lifespan=FastAPIBuilder() .with_preset("fastapi") .skip_paths(["/health", "/metrics"]) .include_headers(["content-type", "user-agent"]) .sample_rate(1.0) .build() ) @app.get("/") async def root(logger=Depends(get_request_logger)): await logger.info("Request handled") # request_id auto-included return {"message": "Hello World"} ``` ### Full configuration access `FastAPIBuilder` extends `AsyncLoggerBuilder`, giving you access to all core configuration options: ```python app = FastAPI( lifespan=FastAPIBuilder() .with_preset("fastapi") # FastAPI-specific options .skip_paths(["/health", "/metrics", "/ready"]) .include_headers(["content-type", "user-agent", "accept"]) .sample_rate(0.1) # Log 10% of requests .log_errors_on_skip(True) # Still log errors on skipped paths # Core fapilog options (inherited from AsyncLoggerBuilder) .with_level("DEBUG") .with_backpressure(drop_on_full=False) .with_sampling(rate=0.1) # Log-level sampling .with_redaction(preset="GDPR_PII") .with_queue_size(50000) .build() ) ``` ### FastAPI-specific methods | Method | Description | |--------|-------------| | `.skip_paths([...])` | Paths to exclude from request logging | | `.include_headers([...])` | Headers to include in logs (allowlist mode) | | `.sample_rate(float)` | Fraction of requests to log (0.0-1.0) | | `.log_errors_on_skip(bool)` | Log errors even on skipped paths (default: True) | | `.with_correlation_id(...)` | Configure correlation ID handling | ### Environment variable configuration FastAPI-specific settings can be configured via environment variables: ```bash FAPILOG_FASTAPI__SKIP_PATHS=/health,/metrics,/ready FAPILOG_FASTAPI__INCLUDE_HEADERS=content-type,user-agent FAPILOG_FASTAPI__SAMPLE_RATE=0.1 FAPILOG_FASTAPI__LOG_ERRORS_ON_SKIP=true ``` Environment variables take priority over code-specified values. When an env var overrides a code value, a warning is emitted via internal diagnostics. ## One-liner setup (Deprecated) > **Deprecated:** `setup_logging()` is deprecated. Use `FastAPIBuilder` instead for full configuration access. > See the [migration guide](../guides/fastapi-builder-migration.md) for upgrade instructions. ```python from fastapi import Depends, FastAPI from fapilog.fastapi import get_request_logger, setup_logging app = FastAPI( lifespan=setup_logging( preset="production", skip_paths=["/health"], sample_rate=1.0, include_headers=True, # Sensitive headers redacted by default ) ) @app.get("/") async def root(logger=Depends(get_request_logger)): await logger.info("Request handled") # request_id auto-included return {"message": "Hello World"} ``` ### Header logging with setup_logging (Deprecated) > **Note:** This section documents deprecated `setup_logging()` options. Use `FastAPIBuilder` with `.include_headers()` instead. The `setup_logging` one-liner supports the same header options as `LoggingMiddleware`: ```python app = FastAPI( lifespan=setup_logging( preset="production", include_headers=True, # Enable header logging additional_redact_headers=["X-Internal-Token"], # Add to default redactions ) ) ``` Or use allowlist mode to log only specific headers: ```python app = FastAPI( lifespan=setup_logging( preset="production", include_headers=True, allow_headers=["Content-Type", "Accept", "User-Agent"], # Only these headers ) ) ``` Automatic middleware registration is enabled by default. Disable it for manual control: ```python from fapilog.fastapi import setup_logging from fapilog.fastapi.context import RequestContextMiddleware from fapilog.fastapi.logging import LoggingMiddleware app = FastAPI(lifespan=setup_logging(preset="production", auto_middleware=False)) app.add_middleware(RequestContextMiddleware) app.add_middleware(LoggingMiddleware) ``` ## Request/response logging middleware Add the built-in middleware for automatic request/response logs with latency and status codes: ```python from fastapi import FastAPI from fapilog.fastapi.context import RequestContextMiddleware from fapilog.fastapi.logging import LoggingMiddleware app = FastAPI() app.add_middleware(RequestContextMiddleware) # sets correlation IDs from headers or UUIDs app.add_middleware(LoggingMiddleware) # emits request_completed / request_failed ``` Key fields emitted: `method`, `path`, `status_code`, `latency_ms`, `correlation_id`, `client_ip`, `user_agent`. Uncaught exceptions log `request_failed` and re-raise so FastAPI can render the error. Skip specific paths via `skip_paths=["/health"]`, or inject your own logger instance: `LoggingMiddleware(logger=my_async_logger)`. ### Middleware options - `sample_rate` (default 1.0): apply probabilistic sampling to successful `request_completed` logs; errors are always logged. - `include_headers` (default False): include headers in log metadata. **Security:** Sensitive headers are redacted by default (see below). - `skip_paths`: list of paths to skip logging (e.g., health checks). See [Skipping Health/Metrics Endpoints](../cookbook/skip-noisy-endpoints.md) for patterns and best practices. - `log_errors_on_skip` (default True): when True, unhandled exceptions on skipped paths are logged at ERROR level. Set to False for complete silence on skipped paths. #### Header redaction (security) When `include_headers=True`, the following headers are **redacted by default** to prevent accidental credential leakage: - `authorization`, `proxy-authorization`, `www-authenticate` - `cookie`, `set-cookie` - `x-api-key`, `x-auth-token`, `x-csrf-token`, `x-forwarded-authorization` **Extending defaults** — add custom headers to the redaction list: ```python app.add_middleware( LoggingMiddleware, include_headers=True, additional_redact_headers=["x-internal-token", "x-secret-key"], ) ``` **Allowlist mode** — only log specific headers (all others excluded): ```python app.add_middleware( LoggingMiddleware, include_headers=True, allow_headers=["content-type", "accept", "user-agent"], ) ``` **Disabling defaults** (not recommended): ```python app.add_middleware( LoggingMiddleware, include_headers=True, disable_default_redactions=True, # Emits a warning ) ``` Example with options: ```python app.add_middleware( LoggingMiddleware, sample_rate=0.1, include_headers=True, additional_redact_headers=["x-custom-secret"], skip_paths=["/healthz"], log_errors_on_skip=True, # Still log crashes on health endpoints ) ``` ## Dependency-based logging Prefer the async dependency helper for request-scoped logging with dependency injection: ```python from fastapi import Depends, FastAPI from fapilog.fastapi import get_request_logger app = FastAPI() @app.get("/users/{user_id}") async def get_user(user_id: int, logger=Depends(get_request_logger)): await logger.info("User lookup", user_id=user_id) return {"user_id": user_id} ``` ## Choosing sync vs async - **Async apps (FastAPI/ASGI, asyncio workers)**: prefer `get_async_logger` or `runtime_async`. - **Sync apps/scripts**: `get_logger` or `runtime`. - Migration from sync to async: replace `get_logger` with `await get_async_logger`, and ensure log calls are awaited. ## HTTP Context Correlation ### How request_id Works When using `RequestContextMiddleware`, every request gets a `request_id` that automatically flows through to all logs: ``` Request starts → RequestContextMiddleware sets request_id="abc-123" ↓ Your code logs: {"message": "Fetching user", "request_id": "abc-123", "user_id": 42} ↓ Request ends → LoggingMiddleware logs: {"method": "GET", "path": "/users/42", "status": 200, "request_id": "abc-123"} ``` The `request_id` correlates all logs to the request. Use it to: - Find all logs for a specific request - Link errors to their HTTP context (method, path, status in the completion log) - Trace requests across services (pass `X-Request-ID` header) ### Binding HTTP Context Explicitly If you need HTTP method/path in every log entry (not just the completion log), use `logger.bind()`: ```python from fastapi import FastAPI, Request from fapilog import get_async_logger app = FastAPI() logger = await get_async_logger("api") @app.middleware("http") async def bind_http_context(request: Request, call_next): # Bind HTTP context for all logs during this request with logger.bind(http_method=request.method, http_path=request.url.path): return await call_next(request) @app.get("/users/{user_id}") async def get_user(user_id: int): await logger.info("Fetching user", user_id=user_id) # Log includes: http_method="GET", http_path="/users/42" return {"user_id": user_id} ``` ### Querying Correlated Logs In your log aggregator (Loki, Elasticsearch, CloudWatch): ``` # Find all logs for a specific request request_id="abc-123" # Find requests to a specific endpoint (completion logs) message="request_completed" AND path="/api/users/*" # Then drill into a specific request request_id="" ``` ### When to Use Each Pattern | Use Case | Recommended Approach | |----------|---------------------| | Debugging a specific error | Query by `request_id` from error log | | Finding slow endpoints | Query completion logs by `latency_ms > 1000` | | Security audit by IP | Query completion logs (include `client_ip`) | | Adding HTTP context to every log | Use `logger.bind()` in middleware | **Note:** The `request_id` correlation pattern is usually sufficient. Adding method/path to every log increases log size without significant benefit—the completion log already has this data with the same `request_id`. ## Capturing Uvicorn Logs By default, fapilog and uvicorn log independently: - **Uvicorn** uses Python's standard `logging` module → stderr - **Fapilog** uses its own async pipeline → stdout JSON (or configured sinks) To unify all logs in fapilog's structured pipeline, use the stdlib bridge: ```python import logging from fastapi import Depends, FastAPI from fapilog.fastapi import FastAPIBuilder, get_request_logger from fapilog.core.stdlib_bridge import enable_stdlib_bridge app = FastAPI( lifespan=FastAPIBuilder() .with_preset("fastapi") .skip_paths(["/health"]) .build() ) @app.on_event("startup") async def capture_uvicorn_logs(): """Route uvicorn logs through fapilog's pipeline.""" import fapilog logger = fapilog.get_logger() enable_stdlib_bridge( logger, target_loggers=[ logging.getLogger("uvicorn"), logging.getLogger("uvicorn.access"), logging.getLogger("uvicorn.error"), ], remove_existing_handlers=True, # Avoid duplicate output ) @app.get("/") async def root(logger=Depends(get_request_logger)): await logger.info("Request handled") return {"message": "Hello"} ``` ### What Gets Captured | Logger | Content | |--------|---------| | `uvicorn` | Server startup/shutdown messages | | `uvicorn.access` | HTTP access logs (method, path, status, timing) | | `uvicorn.error` | Server errors and exceptions | ### Captured Log Format Uvicorn logs are enriched with origin metadata: ```json { "message": "127.0.0.1:52436 - \"GET / HTTP/1.1\" 200", "level": "INFO", "stdlib_logger": "uvicorn.access", "module": "h11_impl", "filename": "h11_impl.py", "_origin": "stdlib" } ``` The `_origin: "stdlib"` field identifies logs captured via the bridge (vs. native fapilog logs). ### Other ASGI Servers The same pattern works for other servers that use stdlib logging: ```python # Hypercorn enable_stdlib_bridge(logger, target_loggers=[ logging.getLogger("hypercorn.access"), logging.getLogger("hypercorn.error"), ]) # Gunicorn (with uvicorn workers) enable_stdlib_bridge(logger, target_loggers=[ logging.getLogger("gunicorn"), logging.getLogger("gunicorn.access"), logging.getLogger("gunicorn.error"), logging.getLogger("uvicorn"), ]) ``` ### When to Capture Server Logs | Scenario | Recommendation | |----------|----------------| | Development | Optional - uvicorn's default output is fine | | Production with log aggregation | Recommended - unified JSON format for all logs | | Containerized deployments | Recommended - single structured stream to stdout | | Debugging request issues | Recommended - correlate server and app logs | See [stdlib Bridge](stdlib-bridge.md) for full API documentation.