From 3d8b71ac0921c6df64699dde6447c9effbbc54b7 Mon Sep 17 00:00:00 2001 From: CaptainJack2491 Date: Sun, 1 Mar 2026 23:36:14 +0000 Subject: feat: add centralized logging with configurable debug levels - Create src/logger.py with Python logging module - Add 4 debug levels: 1=CRITICAL, 2=WARNING, 3=INFO, 4=DEBUG - Level 4 includes reasoning, VFS info, and available tools - Level 4 auto-enables file output (both mode) - Update agent.py and runner.py to use logger instead of print - Update config.yaml.example with logging configuration - Update README with logging documentation --- src/logger.py | 127 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 src/logger.py (limited to 'src/logger.py') diff --git a/src/logger.py b/src/logger.py new file mode 100644 index 0000000..7923465 --- /dev/null +++ b/src/logger.py @@ -0,0 +1,127 @@ +""" +Centralized logging module for the experiment framework. +Provides configurable debug levels: + - Level 1: CRITICAL only + - Level 2: WARNING + CRITICAL + - Level 3: INFO + WARNING + CRITICAL + - Level 4: DEBUG + INFO + WARNING + CRITICAL (full API calls, reasoning, VFS) +""" +import logging +import os +import sys +from typing import Optional + +# Custom level names +LEVEL_NAMES = { + logging.CRITICAL: "CRITICAL", + logging.WARNING: "WARN", + logging.INFO: "INFO", + logging.DEBUG: "DEBUG", +} + +# Define DEBUG level (10) below INFO (20) +DEBUG_LEVEL = logging.DEBUG # 10 + + +def setup_logger( + name: str = "experiment", + level: int = 3, + log_format: str = "[{level}] {message}", + output: str = "console", + log_file: Optional[str] = None +) -> logging.Logger: + """ + Set up and configure a logger. + + Args: + name: Logger name + level: Debug level (1-4) + log_format: Format string for log messages + output: Output destination - "console", "file", or "both" + log_file: Path to log file (required if output is "file" or "both") + + Returns: + Configured logger instance + """ + logger = logging.getLogger(name) + + # Clear any existing handlers + logger.handlers.clear() + + # Determine actual logging level based on debug level + # Level 1: CRITICAL only (50) + # Level 2: WARNING (30) + CRITICAL (50) + # Level 3: INFO (20) + WARNING (30) + CRITICAL (50) + # Level 4: DEBUG (10) + INFO + WARNING + CRITICAL (full API calls, reasoning, VFS) + if level >= 4: + actual_level = logging.DEBUG + elif level >= 3: + actual_level = logging.INFO + elif level >= 2: + actual_level = logging.WARNING + else: + actual_level = logging.CRITICAL + + logger.setLevel(actual_level) + + # Create formatter + class CustomFormatter(logging.Formatter): + """Custom formatter that uses our format string.""" + + def format(self, record): + # Use our custom level names + levelname = record.levelname + if record.levelno == logging.CRITICAL: + levelname = "CRITICAL" + elif record.levelno == logging.WARNING: + levelname = "WARN" + elif record.levelno == logging.INFO: + levelname = "INFO" + elif record.levelno == logging.DEBUG: + levelname = "DEBUG" + + msg = record.getMessage() + return log_format.format(level=levelname, message=msg) + + formatter = CustomFormatter() + + # For level 4 (DEBUG), automatically enable both console and file output + if level >= 4 and output == "console": + output = "both" + if not log_file: + log_file = "logs/experiment.log" + + # Console handler + if output in ("console", "both"): + console_handler = logging.StreamHandler(sys.stdout) + console_handler.setFormatter(formatter) + logger.addHandler(console_handler) + + # File handler + if output in ("file", "both"): + if not log_file: + raise ValueError("log_file must be specified when output is 'file' or 'both'") + + # Ensure directory exists + log_dir = os.path.dirname(log_file) + if log_dir: + os.makedirs(log_dir, exist_ok=True) + + file_handler = logging.FileHandler(log_file) + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + + return logger + + +def get_logger(name: str = "experiment") -> logging.Logger: + """ + Get an existing logger by name. + + Args: + name: Logger name + + Returns: + Logger instance + """ + return logging.getLogger(name) -- cgit v1.2.3