diff --git a/nanovllm/utils/logger.py b/nanovllm/utils/logger.py new file mode 100644 index 0000000..15700c1 --- /dev/null +++ b/nanovllm/utils/logger.py @@ -0,0 +1,123 @@ +""" +Global logger for nano-vllm with colored output. + +Usage: + from nanovllm.utils.logger import logger + + logger.debug("Debug message") # Gray + logger.info("Info message") # Blue + logger.warning("Warning message") # Yellow + logger.error("Error message") # Red + +Control log level via environment variable: + NANOVLLM_LOG_LEVEL=DEBUG python your_script.py + NANOVLLM_LOG_LEVEL=INFO python your_script.py (default) + NANOVLLM_LOG_LEVEL=WARNING python your_script.py +""" +import os +import sys +import logging +from typing import Optional + + +# ANSI color codes +class Colors: + RESET = "\033[0m" + GRAY = "\033[90m" + BLUE = "\033[94m" + YELLOW = "\033[93m" + RED = "\033[91m" + BOLD = "\033[1m" + + +# Level -> Color mapping +LEVEL_COLORS = { + logging.DEBUG: Colors.GRAY, + logging.INFO: Colors.BLUE, + logging.WARNING: Colors.YELLOW, + logging.ERROR: Colors.RED, + logging.CRITICAL: Colors.RED + Colors.BOLD, +} + + +class ColoredFormatter(logging.Formatter): + """Formatter that adds colors to log levels.""" + + def __init__(self, fmt: str, datefmt: str = None, use_colors: bool = True): + super().__init__(fmt, datefmt) + self.use_colors = use_colors + + def format(self, record: logging.LogRecord) -> str: + if self.use_colors: + color = LEVEL_COLORS.get(record.levelno, Colors.RESET) + # Color the level name + record.levelname = f"{color}{record.levelname}{Colors.RESET}" + # Color the message + record.msg = f"{color}{record.msg}{Colors.RESET}" + return super().format(record) + + +# Log level from environment variable +_LOG_LEVEL = os.environ.get("NANOVLLM_LOG_LEVEL", "INFO").upper() + +# Global logger instance +_logger: Optional[logging.Logger] = None + + +def _setup_logger() -> logging.Logger: + """Setup and return the global logger.""" + log = logging.getLogger("nanovllm") + + # Avoid duplicate handlers if called multiple times + if log.handlers: + return log + + # Set level from environment + level = getattr(logging, _LOG_LEVEL, logging.INFO) + log.setLevel(level) + + # Create handler (stderr) + handler = logging.StreamHandler(sys.stderr) + handler.setLevel(level) + + # Check if terminal supports colors + use_colors = hasattr(sys.stderr, "isatty") and sys.stderr.isatty() + + # Format: [TIME] [LEVEL] [MODULE] message + formatter = ColoredFormatter( + fmt="[%(asctime)s] [%(levelname)s] [%(name)s] %(message)s", + datefmt="%H:%M:%S", + use_colors=use_colors, + ) + handler.setFormatter(formatter) + + log.addHandler(handler) + + # Don't propagate to root logger + log.propagate = False + + return log + + +def get_logger(name: Optional[str] = None) -> logging.Logger: + """ + Get a logger instance. + + Args: + name: Optional sub-logger name. If provided, creates a child logger + like 'nanovllm.attention'. If None, returns the root nanovllm logger. + + Returns: + Logger instance + """ + global _logger + if _logger is None: + _logger = _setup_logger() + + if name: + return _logger.getChild(name) + return _logger + + +# Convenience: direct access to the root logger +logger = get_logger() \ No newline at end of file