CiteScan / src /core /logging.py
aivolcano
FastAPI + Gradio + src
3d83b62
Raw
History Blame Contribute Delete
3.38 kB
"""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)