""" Production-Grade Structured Logging """ import logging import sys import json from datetime import datetime from typing import Any, Dict from pathlib import Path from pythonjsonlogger import jsonlogger from .config import settings class CustomJsonFormatter(jsonlogger.JsonFormatter): """Custom JSON formatter with additional fields""" def add_fields(self, log_record: Dict[str, Any], record: logging.LogRecord, message_dict: dict): super().add_fields(log_record, record, message_dict) # Add timestamp log_record['timestamp'] = datetime.utcnow().isoformat() # Add log level log_record['level'] = record.levelname # Add application context log_record['app'] = settings.APP_NAME log_record['version'] = settings.APP_VERSION log_record['environment'] = settings.ENVIRONMENT # Add request ID if available (will be set by middleware) if hasattr(record, 'request_id'): log_record['request_id'] = record.request_id # Add user ID if available if hasattr(record, 'user_id'): log_record['user_id'] = record.user_id def setup_logging(): """Configure application logging""" # Create logger logger = logging.getLogger() logger.setLevel(getattr(logging, settings.LOG_LEVEL.upper())) # Remove existing handlers logger.handlers = [] # Console handler console_handler = logging.StreamHandler(sys.stdout) if settings.LOG_FORMAT == "json": console_formatter = CustomJsonFormatter( '%(timestamp)s %(level)s %(name)s %(message)s' ) else: console_formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) console_handler.setFormatter(console_formatter) logger.addHandler(console_handler) # File handler (if configured) if settings.LOG_FILE: log_file = Path(settings.LOG_FILE) log_file.parent.mkdir(parents=True, exist_ok=True) file_handler = logging.FileHandler(log_file) if settings.LOG_FORMAT == "json": file_formatter = CustomJsonFormatter( '%(timestamp)s %(level)s %(name)s %(message)s' ) else: file_formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) file_handler.setFormatter(file_formatter) logger.addHandler(file_handler) return logger # Create module-level logger logger = setup_logging() def log_api_request( method: str, path: str, status_code: int, duration_ms: float, user_id: str = None, request_id: str = None ): """Log API request with structured data""" logger.info( "API Request", extra={ "method": method, "path": path, "status_code": status_code, "duration_ms": duration_ms, "user_id": user_id, "request_id": request_id, "event_type": "api_request" } ) def log_prediction( model_type: str, input_size: int, confidence: float, duration_ms: float, cached: bool = False, user_id: str = None ): """Log ML prediction with metrics""" logger.info( "ML Prediction", extra={ "model_type": model_type, "input_size": input_size, "confidence": confidence, "duration_ms": duration_ms, "cached": cached, "user_id": user_id, "event_type": "prediction" } ) def log_error( error: Exception, context: Dict[str, Any] = None, user_id: str = None, request_id: str = None ): """Log error with full context""" logger.error( f"Error: {str(error)}", extra={ "error_type": type(error).__name__, "error_message": str(error), "context": context or {}, "user_id": user_id, "request_id": request_id, "event_type": "error" }, exc_info=True ) if __name__ == "__main__": # Test logging logger.info("Application starting") logger.debug("Debug message") logger.warning("Warning message") logger.error("Error message") log_api_request("GET", "/api/v1/health", 200, 5.2, request_id="test-123") log_prediction("deepfake", 1024, 0.95, 125.5, cached=False, user_id="user-1")