| """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, |
| } |
|
|
| |
| if record.exc_info: |
| log_data["exception"] = self.formatException(record.exc_info) |
|
|
| |
| 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", |
| "INFO": "\033[32m", |
| "WARNING": "\033[33m", |
| "ERROR": "\033[31m", |
| "CRITICAL": "\033[35m", |
| } |
| 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.""" |
| |
| log_file_path = Path(settings.log_file) |
| log_file_path.parent.mkdir(parents=True, exist_ok=True) |
|
|
| |
| root_logger = logging.getLogger() |
| root_logger.setLevel(getattr(logging, settings.log_level)) |
|
|
| |
| root_logger.handlers.clear() |
|
|
| |
| 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 = 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) |
|
|
| |
| 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) |
|
|