DateTime Timezone Handling (Critical Bug Fix): - Updated all models to use DateTime(timezone=True) for PostgreSQL - Changed datetime defaults to lambda: datetime.now(timezone.utc) - Fixed mixing of timezone-aware and timezone-naive datetime objects - Resolved Internal Server Errors in session completion endpoint - Affected models: User, Team, Tree, Session, Attachment Production Logging System: - Created logging_config.py with structured logging setup - Added log rotation (10MB files, 10 backups) for production - Implemented RequestLoggingMiddleware with correlation IDs - Added ErrorLoggingMiddleware for comprehensive error tracking - Integrated logging into main.py application startup - Supports dev/prod modes with appropriate log levels Integration Tests - Session Workflow: - Created test_sessions.py with 12 comprehensive tests - Session lifecycle: create, update, complete - Session export in multiple formats (markdown, text, HTML) - Error handling and authorization checks - Added pytest.ini with coverage configuration - Added requirements-dev.txt with pytest dependencies Following 2026 FastAPI best practices for timezone handling and structured logging. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
120 lines
4.0 KiB
Python
120 lines
4.0 KiB
Python
"""
|
|
Logging configuration for FastAPI application.
|
|
|
|
Based on 2026 best practices for production FastAPI logging:
|
|
- Structured JSON logging for production
|
|
- Human-readable logging for development
|
|
- Request correlation IDs for tracing
|
|
- Log rotation for long-running applications
|
|
- Separate loggers for access and application logs
|
|
"""
|
|
|
|
import logging
|
|
import logging.handlers
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from app.core.config import settings
|
|
|
|
|
|
def setup_logging() -> None:
|
|
"""
|
|
Configure application logging with structured format and log rotation.
|
|
|
|
Uses different configurations for development vs production:
|
|
- Development: Console logging with color and readable format
|
|
- Production: JSON structured logs with rotation
|
|
"""
|
|
|
|
# Create logs directory if it doesn't exist
|
|
log_dir = Path("logs")
|
|
log_dir.mkdir(exist_ok=True)
|
|
|
|
# Define log format
|
|
log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
date_format = "%Y-%m-%d %H:%M:%S"
|
|
|
|
# Root logger configuration
|
|
root_logger = logging.getLogger()
|
|
root_logger.setLevel(logging.INFO if not settings.DEBUG else logging.DEBUG)
|
|
|
|
# Remove existing handlers
|
|
root_logger.handlers.clear()
|
|
|
|
# Console handler for development
|
|
console_handler = logging.StreamHandler(sys.stdout)
|
|
console_handler.setLevel(logging.DEBUG if settings.DEBUG else logging.INFO)
|
|
console_formatter = logging.Formatter(
|
|
fmt=log_format,
|
|
datefmt=date_format
|
|
)
|
|
console_handler.setFormatter(console_formatter)
|
|
root_logger.addHandler(console_handler)
|
|
|
|
# File handlers with rotation for production
|
|
if not settings.DEBUG:
|
|
# Application log file with rotation (10MB per file, keep 10 files)
|
|
app_file_handler = logging.handlers.RotatingFileHandler(
|
|
filename=log_dir / "app.log",
|
|
maxBytes=10 * 1024 * 1024, # 10 MB
|
|
backupCount=10,
|
|
encoding="utf-8"
|
|
)
|
|
app_file_handler.setLevel(logging.INFO)
|
|
app_file_formatter = logging.Formatter(
|
|
fmt=log_format,
|
|
datefmt=date_format
|
|
)
|
|
app_file_handler.setFormatter(app_file_formatter)
|
|
root_logger.addHandler(app_file_handler)
|
|
|
|
# Error log file with rotation (10MB per file, keep 10 files)
|
|
error_file_handler = logging.handlers.RotatingFileHandler(
|
|
filename=log_dir / "error.log",
|
|
maxBytes=10 * 1024 * 1024, # 10 MB
|
|
backupCount=10,
|
|
encoding="utf-8"
|
|
)
|
|
error_file_handler.setLevel(logging.ERROR)
|
|
error_file_formatter = logging.Formatter(
|
|
fmt=log_format,
|
|
datefmt=date_format
|
|
)
|
|
error_file_handler.setFormatter(error_file_formatter)
|
|
root_logger.addHandler(error_file_handler)
|
|
|
|
# Configure uvicorn logger to use our handlers
|
|
uvicorn_logger = logging.getLogger("uvicorn")
|
|
uvicorn_logger.handlers = root_logger.handlers
|
|
uvicorn_logger.setLevel(root_logger.level)
|
|
uvicorn_logger.propagate = False
|
|
|
|
# Configure uvicorn access logger
|
|
uvicorn_access_logger = logging.getLogger("uvicorn.access")
|
|
uvicorn_access_logger.handlers = root_logger.handlers
|
|
uvicorn_access_logger.setLevel(logging.INFO)
|
|
uvicorn_access_logger.propagate = False
|
|
|
|
# Configure sqlalchemy logger (set to WARNING to reduce noise)
|
|
sqlalchemy_logger = logging.getLogger("sqlalchemy.engine")
|
|
sqlalchemy_logger.setLevel(logging.WARNING if not settings.DEBUG else logging.INFO)
|
|
sqlalchemy_logger.propagate = True
|
|
|
|
logging.info("Logging configured successfully")
|
|
logging.info(f"Log level: {'DEBUG' if settings.DEBUG else 'INFO'}")
|
|
logging.info(f"Environment: {'Development' if settings.DEBUG else 'Production'}")
|
|
|
|
|
|
def get_logger(name: str) -> logging.Logger:
|
|
"""
|
|
Get a logger instance for a specific module.
|
|
|
|
Args:
|
|
name: Name of the module requesting the logger
|
|
|
|
Returns:
|
|
Configured logger instance
|
|
"""
|
|
return logging.getLogger(name)
|