Source code for yt_framework.utils.logging

"""Colored console formatter and helpers used by pipeline startup."""

import logging
import sys
from typing import ClassVar


[docs] class ColoredFormatter(logging.Formatter): """Custom formatter with colors for different log levels.""" # ANSI color codes COLORS: ClassVar[dict[str, str]] = { "DEBUG": "\033[36m", # Cyan "INFO": "\033[32m", # Green "WARNING": "\033[33m", # Yellow "ERROR": "\033[31m", # Red "CRITICAL": "\033[35m", # Magenta } RESET = "\033[0m" # Emoji indicators (only for non-INFO levels to reduce clutter) ICONS: ClassVar[dict[str, str]] = { "DEBUG": "🔍", "WARNING": "⚠️", "ERROR": "✗", "CRITICAL": "🚨", }
[docs] def format(self, record: logging.LogRecord) -> str: """Format log record with colors and icons. Args: record: Log record to format. Returns: str: Formatted log message with ANSI color codes and icons. """ # Add color levelname = record.levelname if levelname in self.COLORS: icon = self.ICONS.get(levelname, "") icon_space = f"{icon} " if icon else "" record.levelname = ( f"{self.COLORS[levelname]}{icon_space}{levelname}{self.RESET}" ) return super().format(record)
[docs] def setup_logging( level: int = logging.INFO, name: str | None = None, use_colors: bool = True, # noqa: FBT001,FBT002 ) -> logging.Logger: """Configure logging with consistent formatting. Args: level: Logging level (default: INFO) name: Logger name (default: root logger) use_colors: Whether to use colored output Returns: Configured logger instance """ logger = logging.getLogger(name) logger.setLevel(level) # If this is a child logger (name is provided), disable propagation # to prevent duplicate messages from propagating to root logger if name is not None: logger.propagate = False # Remove existing handlers logger.handlers.clear() # Console handler console_handler = logging.StreamHandler(sys.stdout) console_handler.setLevel(level) # Formatter with timestamp if use_colors and sys.stdout.isatty(): formatter = ColoredFormatter( "%(asctime)s | %(levelname)s | %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) else: formatter = logging.Formatter( "%(asctime)s | %(levelname)-8s | %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) console_handler.setFormatter(formatter) logger.addHandler(console_handler) return logger
[docs] def log_header(logger: logging.Logger, title: str, context: str | None = None) -> None: """Log a compact section header in format: [Title] context. Args: logger: Logger instance title: Section title (will be wrapped in brackets) context: Optional additional context information """ if context: logger.info("[%s] %s", title, context) else: logger.info("[%s]", title)
[docs] def log_operation(logger: logging.Logger, message: str) -> None: """Log an operation start message with → prefix. Args: logger: Logger instance message: Operation description """ logger.info(" → %s", message)
[docs] def log_success(logger: logging.Logger, message: str) -> None: """Log a success/completion message with ✓ prefix. Args: logger: Logger instance message: Success message """ logger.info(" ✓ %s", message)
[docs] def log_config( logger: logging.Logger, config_dict: dict[str, object], title: str = "Configuration", ) -> None: """Log configuration in a readable format. Automatically masks sensitive values (keys containing 'secret' or 'key') by showing only the last 4 characters. Args: logger: Logger instance config_dict: Configuration dictionary to log title: Title for the configuration section Returns: None Example: >>> config = {"api_key": "secret12345", "mode": "dev"} >>> log_config(logger, config) [Configuration] api_key: ***2345 mode: dev """ log_header(logger, title) for key, value in config_dict.items(): # Mask sensitive data if "secret" in key.lower() or "key" in key.lower(): display = "***" + str(value)[-4:] if value else "(not set)" else: display = value logger.info(" %s: %s", key, display)