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
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)"
)