FastAPI Integration
Complete guide for integrating pretty-loguru with FastAPI applications for enhanced API logging.
🚀 Quick Start
Basic FastAPI Integration
python
from fastapi import FastAPI
from pretty_loguru import create_logger
# Initialize logger
logger = create_logger(
name="demo",
log_path=
folder="logs",
level="INFO"
)
# Create FastAPI app
app = FastAPI(title="My API with pretty-loguru")
@app.on_event("startup")
async def startup_event():
logger.success("🚀 FastAPI application started successfully")
logger.info(f"Logger component: {component_name}")
@app.on_event("shutdown")
async def shutdown_event():
logger.warning("🔴 FastAPI application shutting down")
@app.get("/")
async def root():
logger.info("Root endpoint accessed")
return {"message": "Hello World", "logger": component_name}
@app.get("/health")
async def health_check():
logger.debug("Health check endpoint called")
return {"status": "healthy", "timestamp": "2024-01-01T00:00:00Z"}
Advanced FastAPI Setup
python
from fastapi import FastAPI, Request, HTTPException, Depends
from fastapi.responses import JSONResponse
from pretty_loguru import create_logger
import time
import uuid
from contextlib import asynccontextmanager
# Configure logging for production
create_logger(
level="INFO",
log_path="logs",
component_name="fastapi_app",
rotation="50MB",
retention="30 days"
)
# Add structured logging for APIs
logger.add(
"logs/api_requests.json",
format=lambda record: json.dumps({
"timestamp": record["time"].isoformat(),
"level": record["level"].name,
"message": record["message"],
"request_id": record["extra"].get("request_id"),
"endpoint": record["extra"].get("endpoint"),
"method": record["extra"].get("method"),
"status_code": record["extra"].get("status_code"),
"duration": record["extra"].get("duration"),
"ip": record["extra"].get("ip")
}),
level="INFO",
rotation="daily"
)
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup
logger.ascii_header("FASTAPI STARTUP", font="slant")
logger.success("FastAPI application initialized")
yield
# Shutdown
logger.warning("FastAPI application shutting down")
app = FastAPI(
title="Enhanced API with pretty-loguru",
description="API with comprehensive logging",
version="1.0.1",
lifespan=lifespan
)
🔧 Middleware Integration
Request Logging Middleware
python
from fastapi import FastAPI, Request
from starlette.middleware.base import BaseHTTPMiddleware
import time
import uuid
class LoggingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
# Generate request ID
request_id = str(uuid.uuid4())[:8]
# Log request start
start_time = time.time()
logger.info(
f"🔵 Request started: {request.method} {request.url.path}",
request_id=request_id,
method=request.method,
endpoint=request.url.path,
ip=request.client.host if request.client else "unknown"
)
# Process request
try:
response = await call_next(request)
# Calculate duration
duration = time.time() - start_time
# Log successful response
logger.success(
f"✅ Request completed: {request.method} {request.url.path} - {response.status_code} ({duration:.3f}s)",
request_id=request_id,
method=request.method,
endpoint=request.url.path,
status_code=response.status_code,
duration=duration,
ip=request.client.host if request.client else "unknown"
)
return response
except Exception as e:
duration = time.time() - start_time
logger.error(
f"❌ Request failed: {request.method} {request.url.path} - {str(e)} ({duration:.3f}s)",
request_id=request_id,
method=request.method,
endpoint=request.url.path,
error=str(e),
duration=duration,
ip=request.client.host if request.client else "unknown"
)
raise
# Add middleware to app
app.add_middleware(LoggingMiddleware)
Correlation ID Middleware
python
import contextvars
from starlette.middleware.base import BaseHTTPMiddleware
# Context variable for request correlation
correlation_id_var: contextvars.ContextVar[str] = contextvars.ContextVar('correlation_id')
class CorrelationMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
# Get or generate correlation ID
correlation_id = request.headers.get('X-Correlation-ID', str(uuid.uuid4())[:8])
correlation_id_var.set(correlation_id)
# Bind correlation ID to logger for this request
bound_logger = logger.bind(correlation_id=correlation_id)
bound_logger.info(f"Processing request with correlation ID: {correlation_id}")
try:
response = await call_next(request)
response.headers['X-Correlation-ID'] = correlation_id
return response
except Exception as e:
bound_logger.error(f"Request failed with correlation ID {correlation_id}: {e}")
raise
app.add_middleware(CorrelationMiddleware)
# Usage in endpoints
@app.get("/correlated")
async def correlated_endpoint():
correlation_id = correlation_id_var.get()
logger.bind(correlation_id=correlation_id).info("Processing correlated request")
return {"correlation_id": correlation_id}
🎯 Endpoint-Specific Logging
Dependency Injection for Logging
python
from fastapi import Depends
from typing import Dict, Any
def get_logger_context(request: Request) -> Dict[str, Any]:
"""Dependency to provide logging context"""
return {
"endpoint": request.url.path,
"method": request.method,
"ip": request.client.host if request.client else "unknown",
"user_agent": request.headers.get("user-agent", "unknown")
}
@app.get("/users/{user_id}")
async def get_user(
user_id: int,
context: Dict[str, Any] = Depends(get_logger_context)
):
bound_logger = logger.bind(**context)
bound_logger.info(f"Fetching user {user_id}")
try:
# Simulate user lookup
if user_id == 0:
bound_logger.warning(f"Invalid user ID: {user_id}")
raise HTTPException(status_code=404, detail="User not found")
user_data = {"id": user_id, "name": f"User {user_id}"}
bound_logger.success(f"User {user_id} fetched successfully")
return user_data
except HTTPException:
raise
except Exception as e:
bound_logger.error(f"Error fetching user {user_id}: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
Business Logic Logging
python
from pydantic import BaseModel
from typing import List
class User(BaseModel):
id: int
name: str
email: str
class CreateUserRequest(BaseModel):
name: str
email: str
@app.post("/users", response_model=User)
async def create_user(user_data: CreateUserRequest):
logger.info(f"Creating new user: {user_data.name}")
try:
# Validation logging
if "@" not in user_data.email:
logger.warning(f"Invalid email format: {user_data.email}")
raise HTTPException(status_code=400, detail="Invalid email format")
# Business logic logging
logger.debug(f"Validating user data: {user_data.dict()}")
# Simulate user creation
new_user = User(
id=123,
name=user_data.name,
email=user_data.email
)
logger.success(f"User created successfully: {new_user.id}")
logger.block("User Creation Summary", [
f"ID: {new_user.id}",
f"Name: {new_user.name}",
f"Email: {new_user.email}"
], border_style="green")
return new_user
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to create user: {e}")
raise HTTPException(status_code=500, detail="User creation failed")
@app.get("/users", response_model=List[User])
async def list_users(skip: int = 0, limit: int = 100):
logger.info(f"Listing users with skip={skip}, limit={limit}")
# Simulate user listing
users = [
User(id=i, name=f"User {i}", email=f"user{i}@example.com")
for i in range(skip + 1, skip + limit + 1)
]
logger.success(f"Retrieved {len(users)} users")
return users
🔒 Security & Authentication Logging
Authentication Logging
python
from fastapi import HTTPException, Depends, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
security = HTTPBearer()
async def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
"""Verify JWT token with logging"""
logger.info("Attempting token verification", token_prefix=credentials.credentials[:10])
try:
# Simulate token verification
if credentials.credentials == "invalid":
logger.warning("Invalid token provided", token_prefix=credentials.credentials[:10])
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials"
)
logger.success("Token verified successfully")
return {"user_id": 123, "username": "john_doe"}
except HTTPException:
raise
except Exception as e:
logger.error(f"Token verification failed: {e}")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Authentication failed"
)
@app.get("/protected")
async def protected_endpoint(current_user = Depends(verify_token)):
logger.info(f"Protected endpoint accessed by user {current_user['username']}")
return {"message": "Access granted", "user": current_user}
Security Event Logging
python
from fastapi import Request
import json
def log_security_event(event_type: str, request: Request, details: dict = None):
"""Log security-related events"""
security_data = {
"event_type": event_type,
"ip": request.client.host if request.client else "unknown",
"user_agent": request.headers.get("user-agent", "unknown"),
"endpoint": request.url.path,
"method": request.method,
"timestamp": time.time(),
"details": details or {}
}
logger.bind(security=True, **security_data).warning(
f"Security event: {event_type}"
)
@app.post("/login")
async def login(request: Request, credentials: dict):
username = credentials.get("username")
password = credentials.get("password")
logger.info(f"Login attempt for user: {username}")
# Simulate authentication
if username == "admin" and password == "secret":
log_security_event("login_success", request, {"username": username})
logger.success(f"Successful login for user: {username}")
return {"token": "fake-jwt-token"}
else:
log_security_event("login_failure", request, {
"username": username,
"reason": "invalid_credentials"
})
logger.warning(f"Failed login attempt for user: {username}")
raise HTTPException(status_code=401, detail="Invalid credentials")
📊 Performance Monitoring
Response Time Logging
python
import asyncio
from functools import wraps
def log_performance(operation_name: str):
"""Decorator to log operation performance"""
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
start_time = time.time()
logger.debug(f"Starting {operation_name}")
try:
result = await func(*args, **kwargs)
duration = time.time() - start_time
if duration > 1.0: # Log slow operations
logger.warning(
f"Slow operation: {operation_name} took {duration:.3f}s",
operation=operation_name,
duration=duration,
performance=True
)
else:
logger.debug(
f"Operation completed: {operation_name} ({duration:.3f}s)",
operation=operation_name,
duration=duration,
performance=True
)
return result
except Exception as e:
duration = time.time() - start_time
logger.error(
f"Operation failed: {operation_name} after {duration:.3f}s - {e}",
operation=operation_name,
duration=duration,
error=str(e)
)
raise
return wrapper
return decorator
@app.get("/slow-operation")
@log_performance("slow_database_query")
async def slow_operation():
# Simulate slow operation
await asyncio.sleep(2)
return {"result": "Operation completed"}
Database Query Logging
python
from contextlib import asynccontextmanager
@asynccontextmanager
async def log_database_operation(operation: str, **context):
"""Context manager for database operation logging"""
start_time = time.time()
logger.debug(f"Database operation started: {operation}", **context)
try:
yield
duration = time.time() - start_time
logger.info(
f"Database operation completed: {operation} ({duration:.3f}s)",
operation=operation,
duration=duration,
database=True,
**context
)
except Exception as e:
duration = time.time() - start_time
logger.error(
f"Database operation failed: {operation} after {duration:.3f}s - {e}",
operation=operation,
duration=duration,
error=str(e),
database=True,
**context
)
raise
@app.get("/users/{user_id}/orders")
async def get_user_orders(user_id: int):
async with log_database_operation("fetch_user_orders", user_id=user_id):
# Simulate database query
await asyncio.sleep(0.1)
orders = [
{"id": 1, "product": "Widget A", "price": 19.99},
{"id": 2, "product": "Widget B", "price": 29.99}
]
logger.success(f"Retrieved {len(orders)} orders for user {user_id}")
return orders
🔥 Error Handling & Exception Logging
Global Exception Handler
python
from fastapi import Request, HTTPException
from fastapi.responses import JSONResponse
import traceback
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
"""Global exception handler with comprehensive logging"""
# Generate error ID for tracking
error_id = str(uuid.uuid4())[:8]
# Log detailed error information
logger.error(
f"Unhandled exception [{error_id}]: {type(exc).__name__}: {str(exc)}",
error_id=error_id,
exception_type=type(exc).__name__,
exception_message=str(exc),
endpoint=request.url.path,
method=request.method,
traceback=traceback.format_exc()
)
# Return user-friendly error response
return JSONResponse(
status_code=500,
content={
"error": "Internal server error",
"error_id": error_id,
"message": "An unexpected error occurred. Please contact support with this error ID."
}
)
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
"""HTTP exception handler with logging"""
logger.warning(
f"HTTP exception: {exc.status_code} - {exc.detail}",
status_code=exc.status_code,
detail=exc.detail,
endpoint=request.url.path,
method=request.method
)
return JSONResponse(
status_code=exc.status_code,
content={"error": exc.detail}
)
Custom Exception Classes
python
class BusinessLogicError(Exception):
"""Custom exception for business logic errors"""
def __init__(self, message: str, error_code: str = None):
self.message = message
self.error_code = error_code
super().__init__(self.message)
@app.exception_handler(BusinessLogicError)
async def business_logic_exception_handler(request: Request, exc: BusinessLogicError):
"""Handle business logic exceptions"""
logger.warning(
f"Business logic error: {exc.message}",
error_code=exc.error_code,
endpoint=request.url.path,
method=request.method,
business_logic=True
)
return JSONResponse(
status_code=400,
content={
"error": "Business logic error",
"message": exc.message,
"error_code": exc.error_code
}
)
# Usage in endpoints
@app.post("/process-order")
async def process_order(order_data: dict):
try:
if order_data.get("amount", 0) <= 0:
raise BusinessLogicError("Order amount must be positive", "INVALID_AMOUNT")
logger.info(f"Processing order: {order_data}")
# Process order logic here
return {"status": "success", "order_id": 12345}
except BusinessLogicError:
raise
except Exception as e:
logger.error(f"Unexpected error processing order: {e}")
raise HTTPException(status_code=500, detail="Order processing failed")
This comprehensive FastAPI integration provides enterprise-grade logging capabilities for modern web applications!