"""Logging configuration for CiteScan.""" import logging import sys from pathlib import Path from typing import Any import json from datetime import datetime from .config import settings class JSONFormatter(logging.Formatter): """Custom JSON formatter for structured logging.""" def format(self, record: logging.LogRecord) -> str: """Format log record as JSON.""" log_data: dict[str, Any] = { "timestamp": datetime.utcnow().isoformat(), "level": record.levelname, "logger": record.name, "message": record.getMessage(), "module": record.module, "function": record.funcName, "line": record.lineno, } # Add exception info if present if record.exc_info: log_data["exception"] = self.formatException(record.exc_info) # Add extra fields if hasattr(record, "extra"): log_data.update(record.extra) return json.dumps(log_data, ensure_ascii=False) class TextFormatter(logging.Formatter): """Custom text formatter with colors for console output.""" COLORS = { "DEBUG": "\033[36m", # Cyan "INFO": "\033[32m", # Green "WARNING": "\033[33m", # Yellow "ERROR": "\033[31m", # Red "CRITICAL": "\033[35m", # Magenta } RESET = "\033[0m" def format(self, record: logging.LogRecord) -> str: """Format log record with colors.""" color = self.COLORS.get(record.levelname, self.RESET) record.levelname = f"{color}{record.levelname}{self.RESET}" return super().format(record) def setup_logging() -> None: """Setup logging configuration based on settings.""" # Create logs directory if it doesn't exist log_file_path = Path(settings.log_file) log_file_path.parent.mkdir(parents=True, exist_ok=True) # Root logger root_logger = logging.getLogger() root_logger.setLevel(getattr(logging, settings.log_level)) # Remove existing handlers root_logger.handlers.clear() # Console handler console_handler = logging.StreamHandler(sys.stdout) console_handler.setLevel(getattr(logging, settings.log_level)) if settings.log_format == "json": console_formatter = JSONFormatter() else: console_formatter = TextFormatter( fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S" ) console_handler.setFormatter(console_formatter) root_logger.addHandler(console_handler) # File handler (always JSON for easier parsing) file_handler = logging.FileHandler(log_file_path, encoding="utf-8") file_handler.setLevel(getattr(logging, settings.log_level)) file_handler.setFormatter(JSONFormatter()) root_logger.addHandler(file_handler) # Reduce noise from third-party libraries logging.getLogger("urllib3").setLevel(logging.WARNING) logging.getLogger("httpx").setLevel(logging.WARNING) logging.getLogger("httpcore").setLevel(logging.WARNING) logging.getLogger("asyncio").setLevel(logging.WARNING) def get_logger(name: str) -> logging.Logger: """Get a logger instance with the given name. Args: name: Logger name (typically __name__) Returns: Logger instance """ return logging.getLogger(name)