fastapi-logging

FastAPI Logging & Correlation IDs

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "fastapi-logging" with this command: npx skills add agusmdev/burntop/agusmdev-burntop-fastapi-logging

FastAPI Logging & Correlation IDs

Overview

This skill covers setting up structured JSON logging with correlation ID middleware for request tracing across the application.

Create logging.py

Create src/app/logging.py :

import logging import sys from typing import Any

from pythonjsonlogger import jsonlogger

from app.config import settings

class CustomJsonFormatter(jsonlogger.JsonFormatter): """ Custom JSON formatter that adds standard fields to all log records.

Output format:
{
    "timestamp": "2025-01-05T12:00:00.000Z",
    "level": "INFO",
    "message": "Request completed",
    "logger": "app.api.v1.items",
    "correlation_id": "uuid",
    ...additional fields
}
"""

def add_fields(
    self,
    log_record: dict[str, Any],
    record: logging.LogRecord,
    message_dict: dict[str, Any],
) -> None:
    super().add_fields(log_record, record, message_dict)

    # Standard fields
    log_record["timestamp"] = self.formatTime(record, self.datefmt)
    log_record["level"] = record.levelname
    log_record["logger"] = record.name

    # Remove default fields we're replacing
    log_record.pop("levelname", None)
    log_record.pop("name", None)

    # Add correlation_id from context if available
    from app.middleware.correlation_id import get_correlation_id

    correlation_id = get_correlation_id()
    if correlation_id:
        log_record["correlation_id"] = correlation_id

def setup_logging() -> None: """ Configure application logging.

Call this early in application startup, before creating the FastAPI app.

Configures:
- JSON format for production (LOG_JSON_FORMAT=true)
- Human-readable format for development
- Log level from settings
- Suppresses noisy third-party loggers
"""
log_level = getattr(logging, settings.log_level.upper(), logging.INFO)

# Root logger configuration
root_logger = logging.getLogger()
root_logger.setLevel(log_level)

# Remove existing handlers
root_logger.handlers.clear()

# Create handler
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(log_level)

if settings.log_json_format:
    # JSON format for production
    formatter = CustomJsonFormatter(
        fmt="%(timestamp)s %(level)s %(name)s %(message)s",
        datefmt="%Y-%m-%dT%H:%M:%S.%f",
    )
else:
    # Human-readable format for development
    formatter = logging.Formatter(
        fmt="%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S",
    )

handler.setFormatter(formatter)
root_logger.addHandler(handler)

# Suppress noisy loggers
logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
logging.getLogger("uvicorn.error").setLevel(logging.WARNING)
logging.getLogger("sqlalchemy.engine").setLevel(
    logging.INFO if settings.database_echo else logging.WARNING
)
logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("httpcore").setLevel(logging.WARNING)

def get_logger(name: str) -> logging.Logger: """ Get a logger instance for a module.

Usage:
    logger = get_logger(__name__)
    logger.info("Something happened", extra={"user_id": "123"})

Args:
    name: Logger name (typically __name__)
    
Returns:
    Configured logger instance
"""
return logging.getLogger(name)

Create middleware/correlation_id.py

Create src/app/middleware/correlation_id.py :

import contextvars from uuid import uuid4

from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint from starlette.requests import Request from starlette.responses import Response

Context variable to store correlation ID for the current request

_correlation_id_ctx: contextvars.ContextVar[str | None] = contextvars.ContextVar( "correlation_id", default=None, )

Header name for correlation ID

CORRELATION_ID_HEADER = "X-Correlation-ID"

def get_correlation_id() -> str | None: """ Get the correlation ID for the current request context.

Returns None if called outside of a request context.

Usage:
    from app.middleware.correlation_id import get_correlation_id
    
    correlation_id = get_correlation_id()
    logger.info("Processing", extra={"correlation_id": correlation_id})
"""
return _correlation_id_ctx.get()

def set_correlation_id(correlation_id: str) -> None: """ Set the correlation ID for the current context.

Typically called by middleware, but can be used in tests
or background tasks.
"""
_correlation_id_ctx.set(correlation_id)

class CorrelationIdMiddleware(BaseHTTPMiddleware): """ Middleware that manages correlation IDs for request tracing.

Behavior:
1. Checks for X-Correlation-ID header in incoming request
2. If present, uses that ID (for distributed tracing)
3. If absent, generates a new UUID
4. Stores ID in context variable (accessible via get_correlation_id())
5. Adds ID to response headers

This enables tracing requests across:
- Multiple services (when ID is passed in headers)
- Log aggregation systems
- Error tracking systems
"""

async def dispatch(
    self,
    request: Request,
    call_next: RequestResponseEndpoint,
) -> Response:
    # Get correlation ID from header or generate new one
    correlation_id = request.headers.get(CORRELATION_ID_HEADER)
    if not correlation_id:
        correlation_id = str(uuid4())

    # Store in context variable
    set_correlation_id(correlation_id)

    # Process request
    response = await call_next(request)

    # Add to response headers
    response.headers[CORRELATION_ID_HEADER] = correlation_id

    return response

Create middleware/init.py

Create src/app/middleware/init.py :

from app.middleware.correlation_id import ( CORRELATION_ID_HEADER, CorrelationIdMiddleware, get_correlation_id, set_correlation_id, )

all = [ "CORRELATION_ID_HEADER", "CorrelationIdMiddleware", "get_correlation_id", "set_correlation_id", ]

Usage in Application

In main.py

from app.logging import setup_logging from app.middleware import CorrelationIdMiddleware

Setup logging BEFORE creating app

setup_logging()

def create_app() -> FastAPI: app = FastAPI(...)

# Add correlation ID middleware
app.add_middleware(CorrelationIdMiddleware)

return app

In Services/Routers

from app.logging import get_logger

logger = get_logger(name)

class ItemService: async def create(self, obj_in: ItemCreate) -> Item: logger.info( "Creating item", extra={ "item_name": obj_in.name, "category_id": str(obj_in.category_id), }, )

    item = await self._repository.create(obj_in)
    
    logger.info(
        "Item created successfully",
        extra={"item_id": str(item.id)},
    )
    
    return item

In Exception Handlers

from app.middleware.correlation_id import get_correlation_id

async def app_exception_handler(request: Request, exc: AppException): correlation_id = get_correlation_id()

logger.warning(
    "Application error",
    extra={
        "error_code": exc.error_code,
        "correlation_id": correlation_id,
    },
)

return JSONResponse(
    content={
        "detail": exc.message,
        "correlation_id": correlation_id,
        ...
    }
)

Log Output Examples

JSON Format (Production)

{ "timestamp": "2025-01-05T12:00:00.123456", "level": "INFO", "logger": "app.items.service", "message": "Creating item", "correlation_id": "550e8400-e29b-41d4-a716-446655440000", "item_name": "Widget", "category_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }

Human-Readable Format (Development)

2025-01-05 12:00:00 | INFO | app.items.service | Creating item

Logging Best Practices

  1. Use Appropriate Log Levels

Level Use Case

DEBUG

Detailed diagnostic information

INFO

Routine operations, state changes

WARNING

Unexpected but handled situations

ERROR

Errors that need attention

CRITICAL

System failures

  1. Include Contextual Information

Good - includes context

logger.info( "Order processed", extra={ "order_id": str(order.id), "total": order.total, "items_count": len(order.items), }, )

Bad - no context

logger.info("Order processed")

  1. Log at Boundaries
  • Log at entry/exit of services

  • Log external API calls

  • Log database operations (sparingly)

  1. Don't Log Sensitive Data

Bad - logs password

logger.info(f"User login: {user.email}, password: {user.password}")

Good - no sensitive data

logger.info("User login attempt", extra={"email": user.email})

  1. Use Structured Fields

Good - structured

logger.error( "Database query failed", extra={ "query": "SELECT * FROM items", "duration_ms": 1500, "error": str(e), }, )

Bad - unstructured

logger.error(f"Database query failed: {e}")

Request Logging Middleware (Optional)

For detailed request/response logging:

import time from app.logging import get_logger

logger = get_logger("app.http")

class RequestLoggingMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): start_time = time.perf_counter()

    response = await call_next(request)
    
    duration_ms = (time.perf_counter() - start_time) * 1000
    
    logger.info(
        "Request completed",
        extra={
            "method": request.method,
            "path": request.url.path,
            "status_code": response.status_code,
            "duration_ms": round(duration_ms, 2),
        },
    )
    
    return response

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

fastapi-testing

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

fastapi-exceptions

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

fastapi-app-factory

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

openclaw-version-monitor

监控 OpenClaw GitHub 版本更新,获取最新版本发布说明,翻译成中文, 并推送到 Telegram 和 Feishu。用于:(1) 定时检查版本更新 (2) 推送版本更新通知 (3) 生成中文版发布说明

Archived SourceRecently Updated