Spaces:
Sleeping
Sleeping
""" | |
BioLogger - A comprehensive logging utility for the bio RAG server. | |
This module provides a centralized logging system with correlation ID support, | |
structured logging, and configurable output handlers. | |
""" | |
import sys | |
import traceback | |
from pathlib import Path | |
from typing import Any, Optional | |
from asgi_correlation_id import correlation_id | |
from loguru import logger | |
class BioLogger: | |
""" | |
Enhanced logging utility with correlation ID support and structured logging. | |
This class provides a unified interface for logging with automatic | |
correlation ID binding and comprehensive error tracking. | |
""" | |
def __init__(self, log_dir: str = "logs", max_retention_days: int = 30): | |
""" | |
Initialize the BioLogger. | |
Args: | |
log_dir: Directory to store log files | |
max_retention_days: Maximum number of days to retain log files | |
""" | |
self.log_dir = Path(log_dir) | |
self.max_retention_days = max_retention_days | |
self._setup_logging() | |
def _setup_logging(self) -> None: | |
"""Configure loguru logger with handlers.""" | |
# Remove default handler | |
logger.remove() | |
# Create log directory | |
self.log_dir.mkdir(exist_ok=True) | |
# Terminal handler | |
logger.add( | |
sys.stderr, | |
format=self._get_format_string(), | |
level="INFO", | |
colorize=True, | |
backtrace=True, | |
diagnose=True, | |
) | |
# File handlers | |
log_file = self.log_dir / "bio_rag_{time:YYYY-MM-DD}.log" | |
# Info level file handler | |
logger.add( | |
str(log_file), | |
format=self._get_format_string(), | |
level="INFO", | |
rotation="1 day", | |
retention=f"{self.max_retention_days} days", | |
compression="zip", | |
backtrace=True, | |
diagnose=True, | |
) | |
# Error level file handler | |
logger.add( | |
str(log_file), | |
format=self._get_format_string(), | |
level="ERROR", | |
rotation="1 day", | |
retention=f"{self.max_retention_days} days", | |
compression="zip", | |
backtrace=True, | |
diagnose=True, | |
) | |
def _get_format_string(self) -> str: | |
"""Get the log format string with correlation ID.""" | |
return "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | [CID:{extra[correlation_id]}] | {name}:{function}:{line} | {message}" | |
def _get_correlation_id(self) -> str: | |
"""Get the current correlation ID or return SYSTEM.""" | |
return correlation_id.get() or "SYSTEM" | |
def _bind_logger(self): | |
"""Bind logger with current correlation ID.""" | |
return logger.bind(correlation_id=self._get_correlation_id()) | |
def debug(self, message: str, **kwargs: Any) -> None: | |
""" | |
Log a debug message. | |
Args: | |
message: The message to log | |
**kwargs: Additional context data | |
""" | |
self._bind_logger().debug(message, **kwargs) | |
def info(self, message: str, **kwargs: Any) -> None: | |
""" | |
Log an info message. | |
Args: | |
message: The message to log | |
**kwargs: Additional context data | |
""" | |
self._bind_logger().info(message, **kwargs) | |
def warning(self, message: str, **kwargs: Any) -> None: | |
""" | |
Log a warning message. | |
Args: | |
message: The message to log | |
**kwargs: Additional context data | |
""" | |
self._bind_logger().warning(message, **kwargs) | |
def error( | |
self, message: str, exc_info: Optional[Exception] = None, **kwargs: Any | |
) -> None: | |
""" | |
Log an error message with optional exception information. | |
Args: | |
message: The error message | |
exc_info: Optional exception object for detailed error tracking | |
**kwargs: Additional context data | |
""" | |
if exc_info is not None: | |
error_details = self._format_exception_details(message, exc_info) | |
self._bind_logger().error(error_details, **kwargs) | |
else: | |
self._bind_logger().error(message, **kwargs) | |
def critical( | |
self, message: str, exc_info: Optional[Exception] = None, **kwargs: Any | |
) -> None: | |
""" | |
Log a critical error message. | |
Args: | |
message: The critical error message | |
exc_info: Optional exception object for detailed error tracking | |
**kwargs: Additional context data | |
""" | |
if exc_info is not None: | |
error_details = self._format_exception_details(message, exc_info) | |
self._bind_logger().critical(error_details, **kwargs) | |
else: | |
self._bind_logger().critical(message, **kwargs) | |
def _format_exception_details(self, message: str, exc_info: Exception) -> str: | |
""" | |
Format exception details for logging. | |
Args: | |
message: The base error message | |
exc_info: The exception object | |
Returns: | |
Formatted error details string | |
""" | |
exc_type = exc_info.__class__.__name__ | |
exc_message = str(exc_info) | |
# Get stack trace | |
stack_trace = [] | |
if exc_info.__traceback__: | |
tb_list = traceback.extract_tb(exc_info.__traceback__) | |
for tb in tb_list: | |
stack_trace.append( | |
f" File: {tb.filename}, " | |
f"Line: {tb.lineno}, " | |
f"Function: {tb.name}" | |
) | |
# Format error details | |
error_details = [ | |
f"Error Message: {message}", | |
f"Exception Type: {exc_type}", | |
f"Exception Details: {exc_message}", | |
] | |
if stack_trace: | |
error_details.append("Stack Trace:") | |
error_details.extend(stack_trace) | |
return "\n".join(error_details) | |
def log_performance(self, operation: str, duration: float, **kwargs: Any) -> None: | |
""" | |
Log performance metrics. | |
Args: | |
operation: Name of the operation | |
duration: Duration in seconds | |
**kwargs: Additional performance metrics | |
""" | |
message = f"Performance: {operation} took {duration:.3f}s" | |
if kwargs: | |
metrics = ", ".join(f"{k}={v}" for k, v in kwargs.items()) | |
message += f" | {metrics}" | |
self.info(message) | |
def log_api_call( | |
self, method: str, url: str, status_code: int, duration: float | |
) -> None: | |
""" | |
Log API call details. | |
Args: | |
method: HTTP method | |
url: API endpoint URL | |
status_code: HTTP status code | |
duration: Request duration in seconds | |
""" | |
level = "error" if status_code >= 400 else "info" | |
message = f"API Call: {method} {url} -> {status_code} ({duration:.3f}s)" | |
if level == "error": | |
self.error(message) | |
else: | |
self.info(message) | |
def log_database_operation( | |
self, operation: str, table: str, duration: float, **kwargs: Any | |
) -> None: | |
""" | |
Log database operation details. | |
Args: | |
operation: Database operation (SELECT, INSERT, etc.) | |
table: Table name | |
duration: Operation duration in seconds | |
**kwargs: Additional operation details | |
""" | |
message = f"Database: {operation} on {table} took {duration:.3f}s" | |
if kwargs: | |
details = ", ".join(f"{k}={v}" for k, v in kwargs.items()) | |
message += f" | {details}" | |
self.info(message) | |
# Create singleton instance | |
bio_logger = BioLogger() | |