""" Logging configuration for MCP Browser. """ import logging import sys from pathlib import Path from typing import Optional from datetime import datetime class ServerNameAdapter(logging.LoggerAdapter): """Add server name context to log messages.""" def process(self, msg, kwargs): server = self.extra.get('server', 'main') return f"[{server}] {msg}", kwargs def setup_logging(debug: bool = False, log_file: Optional[Path] = None, log_level: Optional[str] = None): """ Configure logging for MCP Browser. Args: debug: Enable debug logging log_file: Optional file to write logs to log_level: Override log level (DEBUG, INFO, WARNING, ERROR) """ # Determine log level if log_level: level = getattr(logging, log_level.upper(), logging.INFO) elif debug: level = logging.DEBUG else: level = logging.INFO # Create formatter if level == logging.DEBUG: # Include timestamp and module for debug formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) else: # Simpler format for normal use formatter = logging.Formatter('%(levelname)s: %(message)s') # Configure root logger root_logger = logging.getLogger() root_logger.setLevel(level) # Remove existing handlers root_logger.handlers.clear() # Console handler (stderr) console_handler = logging.StreamHandler(sys.stderr) console_handler.setFormatter(formatter) root_logger.addHandler(console_handler) # File handler if requested if log_file: file_handler = logging.FileHandler(log_file, mode='a') file_handler.setFormatter(formatter) root_logger.addHandler(file_handler) # Set levels for specific loggers # Suppress some noisy libraries unless in debug mode if level > logging.DEBUG: logging.getLogger('asyncio').setLevel(logging.WARNING) logging.getLogger('urllib3').setLevel(logging.WARNING) def get_logger(name: str, server: Optional[str] = None) -> logging.Logger: """ Get a logger with optional server context. Args: name: Logger name (usually __name__) server: Optional server name for context Returns: Logger or LoggerAdapter with server context """ logger = logging.getLogger(name) if server: return ServerNameAdapter(logger, {'server': server}) return logger class RawIOFilter(logging.Filter): """Filter to only show raw I/O at TRACE level (5).""" def filter(self, record): # Only show raw I/O messages at TRACE level return record.levelno <= 5 or not record.msg.startswith(('>>> ', '<<< ')) # Add custom TRACE level for raw I/O TRACE = 5 logging.addLevelName(TRACE, 'TRACE') def trace(self, message, *args, **kwargs): if self.isEnabledFor(TRACE): self._log(TRACE, message, args, **kwargs) # Add trace method to Logger class logging.Logger.trace = trace