""" Logging Configuration Provides structured logging setup for the application. Based on Python Quick Start Guide best practices. """ import logging import sys from typing import Optional from datetime import datetime def setup_logging( log_level: str = "INFO", environment: str = "development" ) -> None: """ Configure application logging. Args: log_level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL) environment: Environment name (development, production) Usage: setup_logging(log_level="INFO", environment="production") """ level = getattr(logging, log_level.upper(), logging.INFO) # Clear existing handlers root_logger = logging.getLogger() for handler in root_logger.handlers[:]: root_logger.removeHandler(handler) # Console handler console_handler = logging.StreamHandler(sys.stdout) console_handler.setLevel(level) # Format based on environment if environment == "development": # Human-readable format for development formatter = logging.Formatter( fmt='%(asctime)s | %(levelname)-8s | %(name)s:%(funcName)s:%(lineno)d | %(message)s', datefmt='%H:%M:%S' ) else: # Structured format for production (easier to parse) formatter = logging.Formatter( fmt='%(asctime)s | %(levelname)s | %(name)s | %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) console_handler.setFormatter(formatter) root_logger.addHandler(console_handler) root_logger.setLevel(level) # Suppress noisy loggers logging.getLogger('werkzeug').setLevel(logging.WARNING) logging.getLogger('urllib3').setLevel(logging.WARNING) def get_logger(name: str) -> logging.Logger: """ Get a logger instance. Args: name: Logger name (typically __name__) Returns: Logger instance Example: logger = get_logger(__name__) logger.info("User logged in", extra={"user_id": 123}) """ return logging.getLogger(name) # ========================================== # Structured Logging Helpers # ========================================== def log_audit_event( logger: logging.Logger, action: str, user_id: Optional[int] = None, ip_address: Optional[str] = None, **kwargs ) -> None: """ Log an audit event with structured data. Args: logger: Logger instance action: Action performed (e.g., "login_success", "user_created") user_id: User ID performing action ip_address: IP address of request **kwargs: Additional context data Example: log_audit_event( logger, action="login_success", user_id=123, ip_address="192.168.1.1", username="admin" ) """ extra_data = { 'action': action, 'user_id': user_id, 'ip_address': ip_address, 'timestamp': datetime.utcnow().isoformat(), **kwargs } # Filter out None values extra_data = {k: v for k, v in extra_data.items() if v is not None} logger.info(f"AUDIT: {action}", extra=extra_data) def log_security_event( logger: logging.Logger, event_type: str, severity: str = "warning", **kwargs ) -> None: """ Log a security-related event. Args: logger: Logger instance event_type: Type of security event (e.g., "failed_login", "rate_limit_exceeded") severity: Severity level (debug, info, warning, error, critical) **kwargs: Additional context data Example: log_security_event( logger, event_type="failed_login", severity="warning", username="admin", ip_address="192.168.1.1", attempts=3 ) """ extra_data = { 'event_type': event_type, 'timestamp': datetime.utcnow().isoformat(), **kwargs } log_method = getattr(logger, severity.lower(), logger.warning) log_method(f"SECURITY: {event_type}", extra=extra_data) # ========================================== # Request Logging Helpers # ========================================== def log_request( logger: logging.Logger, method: str, path: str, status_code: int, duration_ms: float, user_id: Optional[int] = None ) -> None: """ Log an HTTP request. Args: logger: Logger instance method: HTTP method (GET, POST, etc.) path: Request path status_code: HTTP status code duration_ms: Request duration in milliseconds user_id: Authenticated user ID (if any) Example: log_request( logger, method="POST", path="/api/users", status_code=201, duration_ms=45.2, user_id=123 ) """ extra_data = { 'method': method, 'path': path, 'status_code': status_code, 'duration_ms': round(duration_ms, 2), 'user_id': user_id } # Filter out None values extra_data = {k: v for k, v in extra_data.items() if v is not None} # Use different log levels based on status code if status_code >= 500: logger.error(f"{method} {path} {status_code}", extra=extra_data) elif status_code >= 400: logger.warning(f"{method} {path} {status_code}", extra=extra_data) else: logger.info(f"{method} {path} {status_code}", extra=extra_data) # ========================================== # Error Logging Helpers # ========================================== def log_exception( logger: logging.Logger, error: Exception, context: Optional[str] = None, **kwargs ) -> None: """ Log an exception with context. Args: logger: Logger instance error: Exception instance context: Additional context about where error occurred **kwargs: Additional context data Example: try: # Some operation except Exception as e: log_exception( logger, error=e, context="Failed to create user", user_id=123 ) """ extra_data = { 'error_type': type(error).__name__, 'error_message': str(error), 'context': context, **kwargs } # Filter out None values extra_data = {k: v for k, v in extra_data.items() if v is not None} logger.error( f"Exception: {type(error).__name__}: {str(error)}", extra=extra_data, exc_info=True )