FastAPI / ASGI Integration
For a complete, production-ready example with health checks, metrics, and graceful shutdown, see the FastAPI Production Template.
FastAPIBuilder (Recommended)
FastAPIBuilder provides the full power of fapilog’s configuration with FastAPI-specific additions:
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:
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 |
|---|---|
|
Paths to exclude from request logging |
|
Headers to include in logs (allowlist mode) |
|
Fraction of requests to log (0.0-1.0) |
|
Log errors even on skipped paths (default: True) |
|
Configure correlation ID handling |
Environment variable configuration
FastAPI-specific settings can be configured via environment variables:
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. UseFastAPIBuilderinstead for full configuration access. See the migration guide for upgrade instructions.
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. UseFastAPIBuilderwith.include_headers()instead.
The setup_logging one-liner supports the same header options as LoggingMiddleware:
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:
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:
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:
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 successfulrequest_completedlogs; 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 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-authenticatecookie,set-cookiex-api-key,x-auth-token,x-csrf-token,x-forwarded-authorization
Extending defaults — add custom headers to the redaction list:
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):
app.add_middleware(
LoggingMiddleware,
include_headers=True,
allow_headers=["content-type", "accept", "user-agent"],
)
Disabling defaults (not recommended):
app.add_middleware(
LoggingMiddleware,
include_headers=True,
disable_default_redactions=True, # Emits a warning
)
Example with options:
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:
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_loggerorruntime_async.Sync apps/scripts:
get_loggerorruntime.Migration from sync to async: replace
get_loggerwithawait 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-IDheader)
Binding HTTP Context Explicitly
If you need HTTP method/path in every log entry (not just the completion log), use logger.bind():
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}
When to Use Each Pattern
Use Case |
Recommended Approach |
|---|---|
Debugging a specific error |
Query by |
Finding slow endpoints |
Query completion logs by |
Security audit by IP |
Query completion logs (include |
Adding HTTP context to every log |
Use |
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
loggingmodule → stderrFapilog uses its own async pipeline → stdout JSON (or configured sinks)
To unify all logs in fapilog’s structured pipeline, use the stdlib bridge:
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 |
|---|---|
|
Server startup/shutdown messages |
|
HTTP access logs (method, path, status, timing) |
|
Server errors and exceptions |
Captured Log Format
Uvicorn logs are enriched with origin metadata:
{
"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:
# 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 for full API documentation.