Source code for tooluniverse.logging_config

"""
Global logging configuration for ToolUniverse

This module provides a centralized logging system based on Python's standard logging module.
It allows controlling debug output across the entire ToolUniverse project with different
verbosity levels.

Usage:
    # Set log level via environment variable
    export TOOLUNIVERSE_LOG_LEVEL=DEBUG

    # Or set programmatically
    from tooluniverse.logging_config import setup_logging, get_logger
    setup_logging('DEBUG')

    # Use in your code
    logger = get_logger(__name__)
    logger.info("This is an info message")
    logger.debug("This is a debug message")
"""

import logging
import os
import sys
from typing import Optional, Callable

# Define custom log levels
PROGRESS_LEVEL = 25  # Between INFO(20) and WARNING(30)
logging.addLevelName(PROGRESS_LEVEL, "PROGRESS")


[docs] class ToolUniverseFormatter(logging.Formatter): """Custom formatter with colored output and emoji prefixes""" # Color codes for different log levels COLORS = { "DEBUG": "\033[36m", # Cyan "INFO": "\033[32m", # Green "PROGRESS": "\033[34m", # Blue "WARNING": "\033[33m", # Yellow "ERROR": "\033[31m", # Red "CRITICAL": "\033[35m", # Magenta "RESET": "\033[0m", # Reset } # Emoji prefixes for different log levels EMOJI_PREFIX = { "DEBUG": "🔧 ", "INFO": "â„šī¸ ", "PROGRESS": "âŗ ", "WARNING": "âš ī¸ ", "ERROR": "❌ ", "CRITICAL": "🚨 ", }
[docs] def format(self, record): # Add emoji prefix emoji = self.EMOJI_PREFIX.get(record.levelname, "") # Add color if output is to terminal if hasattr(sys.stderr, "isatty") and sys.stderr.isatty(): color = self.COLORS.get(record.levelname, "") reset = self.COLORS["RESET"] record.levelname = f"{color}{record.levelname}{reset}" # Format the message formatted = super().format(record) return f"{emoji}{formatted}"
[docs] class ToolUniverseLogger: """ Singleton logger manager for ToolUniverse """ _instance = None _logger = None _initialized = False def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance
[docs] def __init__(self): if not self._initialized: self._setup_logger() self._initialized = True
def _setup_logger(self): """Setup the main ToolUniverse logger""" self._logger = logging.getLogger("tooluniverse") # Remove existing handlers to avoid duplicates for handler in self._logger.handlers[:]: self._logger.removeHandler(handler) # Set initial level from environment or default to INFO initial_level = os.getenv("TOOLUNIVERSE_LOG_LEVEL", "INFO").upper() try: level = getattr(logging, initial_level) except AttributeError: level = logging.INFO self._logger.setLevel(level) # Create console handler handler = logging.StreamHandler(sys.stdout) handler.setLevel(level) # Create formatter formatter = ToolUniverseFormatter( fmt="%(message)s", # Simple format since we add emoji prefix datefmt="%H:%M:%S", ) handler.setFormatter(formatter) # Add handler to logger self._logger.addHandler(handler) # Prevent propagation to root logger self._logger.propagate = False
[docs] def get_logger(self, name: Optional[str] = None) -> logging.Logger: """Get a logger instance""" if name: return logging.getLogger(f"tooluniverse.{name}") # Fallback to module logger if not initialized return ( self._logger if self._logger is not None else logging.getLogger("tooluniverse") )
[docs] def set_level(self, level: str) -> None: """Set logging level""" try: log_level = getattr(logging, level.upper()) if self._logger is None: return self._logger.setLevel(log_level) for handler in self._logger.handlers: handler.setLevel(log_level) except AttributeError: if self._logger is not None: self._logger.warning( f"Invalid log level: {level}. Valid levels: DEBUG, INFO, WARNING, ERROR, CRITICAL" )
# Global logger instance _logger_manager = ToolUniverseLogger()
[docs] def setup_logging(level: Optional[str] = None) -> None: """ Setup global logging configuration Args: level (str): Log level ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL') """ if level: _logger_manager.set_level(level)
[docs] def get_logger(name: Optional[str] = None) -> logging.Logger: """ Get a logger instance Args: name (str, optional): Logger name (usually __name__) Returns: logging.Logger: Logger instance """ return _logger_manager.get_logger(name)
[docs] def set_log_level(level: str) -> None: """Set global log level""" _logger_manager.set_level(level)
# Convenience functions using the main logger _main_logger = get_logger()
[docs] def debug(msg, *args, **kwargs): """Log debug message""" _main_logger.debug(msg, *args, **kwargs)
[docs] def info(msg, *args, **kwargs): """Log info message""" _main_logger.info(msg, *args, **kwargs)
[docs] def progress_log(msg, *args, **kwargs) -> None: """Log progress message""" # Preserve dedicated PROGRESS level without monkey-patching Logger _main_logger.log(PROGRESS_LEVEL, msg, *args, **kwargs)
[docs] def warning(msg, *args, **kwargs): """Log warning message""" _main_logger.warning(msg, *args, **kwargs)
[docs] def error(msg, *args, **kwargs): """Log error message""" _main_logger.error(msg, *args, **kwargs)
[docs] def critical(msg, *args, **kwargs): """Log critical message""" _main_logger.critical(msg, *args, **kwargs)
# For backward compatibility minimal = info # Alias minimal to info verbose = debug # Alias verbose to debug progress: Callable[..., None] = progress_log # Alias for convenience
[docs] def get_hook_logger(name: str = "HookManager") -> logging.Logger: """ Get a logger specifically configured for hook operations. Args: name (str): Name of the logger. Defaults to 'HookManager' Returns: logging.Logger: Configured logger for hook operations """ return get_logger(name)
[docs] def log_hook_execution( hook_name: str, tool_name: str, execution_time: float, success: bool ): """ Log hook execution details for monitoring and debugging. Args: hook_name (str): Name of the hook that was executed tool_name (str): Name of the tool the hook was applied to execution_time (float): Time taken to execute the hook in seconds success (bool): Whether the hook execution was successful """ logger = get_hook_logger() status = "SUCCESS" if success else "FAILED" logger.info( f"Hook {hook_name} for tool {tool_name}: {status} ({execution_time:.2f}s)" )