logging_config.py
1 """ 2 Logging configuration for Ag3ntum. 3 4 Provides unified logging setup utilities for CLI, HTTP client, and API. 5 All modules should use these functions instead of configuring logging directly. 6 7 Usage: 8 from .logging_config import setup_file_logging, setup_dual_logging 9 10 # For CLI (file-only logging) 11 setup_file_logging(log_level="INFO") 12 13 # For API (console + file logging) 14 setup_dual_logging(log_level="DEBUG", loggers=["src.api", "uvicorn"]) 15 """ 16 import logging 17 import sys 18 from logging.handlers import RotatingFileHandler 19 from pathlib import Path 20 from typing import Optional 21 22 import colorlog 23 24 from ..config import LOGS_DIR 25 from .constants import ( 26 COLORLOG_COLORS, 27 LOG_BACKUP_COUNT, 28 LOG_FILE_BACKEND, 29 LOG_FILE_CLI, 30 LOG_FILE_HTTP, 31 LOG_FORMAT_COLORED, 32 LOG_FORMAT_FILE, 33 LOG_MAX_BYTES, 34 ) 35 36 logger = logging.getLogger(__name__) 37 38 39 def _get_log_level(log_level: str) -> int: 40 """ 41 Convert log level string to logging constant. 42 43 Args: 44 log_level: Log level name (DEBUG, INFO, WARNING, ERROR). 45 46 Returns: 47 Logging level constant. 48 """ 49 return getattr(logging, log_level.upper(), logging.INFO) 50 51 52 def _create_rotating_file_handler( 53 log_file: Path, 54 level: int, 55 max_bytes: int = LOG_MAX_BYTES, 56 backup_count: int = LOG_BACKUP_COUNT, 57 ) -> RotatingFileHandler: 58 """ 59 Create a rotating file handler with standard configuration. 60 61 Args: 62 log_file: Path to the log file. 63 level: Logging level. 64 max_bytes: Maximum file size before rotation. 65 backup_count: Number of backup files to keep. 66 67 Returns: 68 Configured RotatingFileHandler. 69 """ 70 log_file.parent.mkdir(parents=True, exist_ok=True) 71 72 handler = RotatingFileHandler( 73 filename=str(log_file), 74 maxBytes=max_bytes, 75 backupCount=backup_count, 76 encoding="utf-8", 77 ) 78 handler.setLevel(level) 79 handler.setFormatter(logging.Formatter(LOG_FORMAT_FILE)) 80 return handler 81 82 83 def _create_console_handler(level: int, colored: bool = False) -> logging.Handler: 84 """ 85 Create a console (stdout) handler. 86 87 Args: 88 level: Logging level. 89 colored: Whether to use colored output. 90 91 Returns: 92 Configured StreamHandler. 93 """ 94 if colored: 95 handler = colorlog.StreamHandler(sys.stdout) 96 handler.setFormatter( 97 colorlog.ColoredFormatter( 98 LOG_FORMAT_COLORED, 99 log_colors=COLORLOG_COLORS, 100 secondary_log_colors={}, 101 style="%", 102 ) 103 ) 104 else: 105 handler = logging.StreamHandler(sys.stdout) 106 handler.setFormatter(logging.Formatter(LOG_FORMAT_FILE)) 107 108 handler.setLevel(level) 109 return handler 110 111 112 def setup_file_logging( 113 log_level: str = "INFO", 114 log_file: Optional[Path] = None, 115 log_name: str = LOG_FILE_CLI, 116 max_bytes: int = LOG_MAX_BYTES, 117 backup_count: int = LOG_BACKUP_COUNT, 118 ) -> None: 119 """ 120 Configure file-only logging (for CLI and HTTP client). 121 122 Replaces all handlers on the root logger with a single rotating file handler. 123 This ensures clean log separation between different entry points. 124 125 Args: 126 log_level: Logging level (DEBUG, INFO, WARNING, ERROR). 127 log_file: Path to log file. If None, uses LOGS_DIR / log_name. 128 log_name: Name of log file (default: agent_cli.log). 129 max_bytes: Maximum size of log file before rotation. 130 backup_count: Number of backup files to keep. 131 """ 132 level = _get_log_level(log_level) 133 134 if log_file is None: 135 log_file = LOGS_DIR / log_name 136 137 file_handler = _create_rotating_file_handler( 138 log_file=log_file, 139 level=level, 140 max_bytes=max_bytes, 141 backup_count=backup_count, 142 ) 143 144 root_logger = logging.getLogger() 145 root_logger.handlers.clear() 146 root_logger.setLevel(level) 147 root_logger.addHandler(file_handler) 148 149 150 def setup_dual_logging( 151 log_level: str = "INFO", 152 log_file: Optional[Path] = None, 153 log_name: str = LOG_FILE_BACKEND, 154 loggers: Optional[list[str]] = None, 155 max_bytes: int = LOG_MAX_BYTES, 156 backup_count: int = LOG_BACKUP_COUNT, 157 ) -> None: 158 """ 159 Configure dual logging: colored console + rotating file. 160 161 For API/backend use where you want both console output for development 162 and file logging for production. Configures specific loggers to prevent 163 log bleeding from other components. 164 165 Args: 166 log_level: Logging level (DEBUG, INFO, WARNING, ERROR). 167 log_file: Path to log file. If None, uses LOGS_DIR / log_name. 168 log_name: Name of log file (default: backend.log). 169 loggers: List of logger names to configure. If None, uses root logger. 170 max_bytes: Maximum size of log file before rotation. 171 backup_count: Number of backup files to keep. 172 """ 173 level = _get_log_level(log_level) 174 175 if log_file is None: 176 log_file = LOGS_DIR / log_name 177 178 file_handler = _create_rotating_file_handler( 179 log_file=log_file, 180 level=level, 181 max_bytes=max_bytes, 182 backup_count=backup_count, 183 ) 184 console_handler = _create_console_handler(level=level, colored=True) 185 186 if loggers is None: 187 # Configure root logger 188 root_logger = logging.getLogger() 189 root_logger.handlers.clear() 190 root_logger.setLevel(level) 191 root_logger.addHandler(file_handler) 192 root_logger.addHandler(console_handler) 193 else: 194 # Configure specific loggers (prevents log bleeding) 195 for logger_name in loggers: 196 log = logging.getLogger(logger_name) 197 log.handlers.clear() 198 log.setLevel(level) 199 log.addHandler(file_handler) 200 log.addHandler(console_handler) 201 log.propagate = False 202 203 204 def setup_cli_logging(log_level: str = "INFO") -> None: 205 """ 206 Configure logging for CLI entry point (agent_cli.py). 207 208 Shorthand for setup_file_logging with CLI defaults. 209 210 Args: 211 log_level: Logging level. 212 """ 213 setup_file_logging(log_level=log_level, log_name=LOG_FILE_CLI) 214 215 216 def setup_http_logging(log_level: str = "INFO") -> None: 217 """ 218 Configure logging for HTTP client entry point (agent_http.py). 219 220 Shorthand for setup_file_logging with HTTP client defaults. 221 222 Args: 223 log_level: Logging level. 224 """ 225 setup_file_logging(log_level=log_level, log_name=LOG_FILE_HTTP) 226 227 228 def setup_backend_logging(log_level: str = "INFO") -> None: 229 """ 230 Configure logging for API backend (src/api/main.py). 231 232 Uses dual logging (console + file) for specific backend loggers. 233 234 Args: 235 log_level: Logging level. 236 """ 237 backend_loggers = [ 238 "src.api", 239 "src.services", 240 "src.core", 241 "src.db", 242 "ag3ntum", 243 "tools.ag3ntum", 244 "uvicorn", 245 "uvicorn.error", 246 "uvicorn.access", 247 "fastapi", 248 ] 249 250 setup_dual_logging( 251 log_level=log_level, 252 log_name=LOG_FILE_BACKEND, 253 loggers=backend_loggers, 254 ) 255 256 # Enable uvicorn access logs for HTTP request tracking 257 logging.getLogger("uvicorn.access").setLevel(logging.INFO)