FastAPI JSON logging to stdout (Uvicorn access logs included)
Container platforms like Docker, Kubernetes, and AWS ECS expect structured JSON logs on stdout. Without consistent formatting, log aggregation systems (CloudWatch, Datadog, ELK) can’t parse your logs reliably.
The Problem
A typical FastAPI application produces two distinct log streams:
Application logs (your code):
INFO: Processing order 12345
Uvicorn access logs (HTTP requests):
INFO: 127.0.0.1:52486 - "GET /api/orders HTTP/1.1" 200 OK
These plain text formats cause problems:
Inconsistent structure - App logs and access logs have different formats
Color codes - Uvicorn adds ANSI escape sequences that break JSON parsers
No machine-readable fields - Can’t filter by status code, latency, or request ID
The Solution
fapilog provides unified JSON output for both application and access logs:
from fastapi import FastAPI, Depends
from fapilog.fastapi import FastAPIBuilder, get_request_logger
app = FastAPI(
lifespan=FastAPIBuilder()
.with_preset("fastapi")
.build()
)
@app.get("/api/orders/{order_id}")
async def get_order(order_id: str, logger=Depends(get_request_logger)):
await logger.info("Processing order", order_id=order_id)
return {"order_id": order_id, "status": "shipped"}
The fastapi preset configuration:
Outputs JSON to stdout (no files)
Disables color codes
Includes request context (request_id) automatically
Masks sensitive fields (passwords, API keys, tokens)
Sample Output
Application log:
{"timestamp": "2026-01-21T10:30:00.123Z", "level": "INFO", "message": "Processing order", "order_id": "12345", "request_id": "550e8400-e29b-41d4-a716-446655440000"}
Access log:
{"timestamp": "2026-01-21T10:30:00.456Z", "level": "INFO", "message": "HTTP request", "method": "GET", "path": "/api/orders/12345", "status_code": 200, "duration_ms": 15.2, "request_id": "550e8400-e29b-41d4-a716-446655440000"}
Both share consistent field names (timestamp, level, message, request_id), making log aggregation straightforward.
Including Uvicorn Access Logs
fapilog’s FastAPIBuilder automatically installs middleware that captures HTTP requests. The LoggingMiddleware handles:
Request method, path, and query parameters
Response status codes
Request duration
Client IP address
Request ID correlation
No additional Uvicorn configuration is needed. The default Uvicorn access log is effectively replaced by fapilog’s structured output.
Disabling Default Uvicorn Logs
If you want to suppress Uvicorn’s default access logging entirely (recommended to avoid duplicate logs), configure Uvicorn:
import uvicorn
if __name__ == "__main__":
uvicorn.run(
"main:app",
host="0.0.0.0",
port=8000,
access_log=False, # Disable Uvicorn's access log
)
Or via CLI:
uvicorn main:app --host 0.0.0.0 --port 8000 --no-access-log
Docker Configuration
A minimal Dockerfile for JSON logging:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# Use --no-access-log to avoid duplicate access logs
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--no-access-log"]
Docker Compose
services:
api:
build: .
ports:
- "8000:8000"
environment:
- FAPILOG_CORE__LOG_LEVEL=INFO
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
Explicit Format Control
If you’re not using the FastAPI integration but still want JSON output:
from fapilog import get_async_logger
async def main():
# format="json" ensures JSON output regardless of environment
logger = await get_async_logger(format="json")
await logger.info("Starting service", version="1.0.0")
For automatic format detection (JSON in containers, pretty in terminals):
logger = await get_async_logger(format="auto")
Going Deeper
FastAPI request_id Logging - Correlation ID middleware
Context Enrichment - Adding custom fields to all logs
Redaction - Masking sensitive data
Why Fapilog? - How fapilog compares to other logging libraries