Spaces:
Running
Running
| """ | |
| Centralized structured logging with rotation and request ID tracking. | |
| """ | |
| import os | |
| import json | |
| import logging | |
| import threading | |
| from logging.handlers import RotatingFileHandler | |
| from typing import Optional | |
| from datetime import datetime | |
| from config import config | |
| # Thread-local storage for request context | |
| _request_context = threading.local() | |
| class RequestContextFilter(logging.Filter): | |
| """Add request ID to log records.""" | |
| def filter(self, record): | |
| record.request_id = getattr(_request_context, 'request_id', 'N/A') | |
| record.user_ip = getattr(_request_context, 'user_ip', 'N/A') | |
| return True | |
| class JSONFormatter(logging.Formatter): | |
| """Format logs as JSON for structured logging.""" | |
| def format(self, record): | |
| log_data = { | |
| 'timestamp': datetime.utcnow().isoformat(), | |
| 'level': record.levelname, | |
| 'logger': record.name, | |
| 'message': record.getMessage(), | |
| 'module': record.module, | |
| 'function': record.funcName, | |
| 'line': record.lineno, | |
| 'request_id': getattr(record, 'request_id', 'N/A'), | |
| 'user_ip': getattr(record, 'user_ip', 'N/A'), | |
| } | |
| # Add exception info if present | |
| if record.exc_info: | |
| log_data['exception'] = self.formatException(record.exc_info) | |
| # Add extra fields | |
| if hasattr(record, 'extra_data'): | |
| log_data['extra'] = record.extra_data | |
| return json.dumps(log_data) | |
| def setup_logger(name: str, log_level: Optional[str] = None) -> logging.Logger: | |
| """ | |
| Create a logger with both file and console handlers. | |
| Args: | |
| name: Logger name (typically __name__) | |
| log_level: Optional override for log level | |
| Returns: | |
| Configured logger instance | |
| """ | |
| logger = logging.getLogger(name) | |
| # Avoid duplicate handlers | |
| if logger.handlers: | |
| return logger | |
| # Set level | |
| level = log_level or config.LOG_LEVEL | |
| logger.setLevel(getattr(logging, level.upper())) | |
| # Add request context filter | |
| logger.addFilter(RequestContextFilter()) | |
| # Console handler (human-readable for development) | |
| console_handler = logging.StreamHandler() | |
| console_handler.setLevel(logging.DEBUG if config.DEBUG else logging.INFO) | |
| if config.ENVIRONMENT.value == "production": | |
| # JSON format for production | |
| console_handler.setFormatter(JSONFormatter()) | |
| else: | |
| # Human-readable format for development | |
| console_format = logging.Formatter( | |
| '%(asctime)s - [%(request_id)s] - %(name)s - %(levelname)s - %(message)s' | |
| ) | |
| console_handler.setFormatter(console_format) | |
| logger.addHandler(console_handler) | |
| # File handler with rotation | |
| try: | |
| log_dir = os.path.dirname(config.LOG_FILE_PATH) | |
| if log_dir: | |
| os.makedirs(log_dir, exist_ok=True) | |
| file_handler = RotatingFileHandler( | |
| config.LOG_FILE_PATH, | |
| maxBytes=config.LOG_MAX_BYTES, | |
| backupCount=config.LOG_BACKUP_COUNT | |
| ) | |
| file_handler.setLevel(logging.DEBUG) | |
| # Always use JSON format for file logs | |
| file_handler.setFormatter(JSONFormatter()) | |
| logger.addHandler(file_handler) | |
| except Exception as e: | |
| logger.warning(f"Failed to setup file logging: {e}") | |
| return logger | |
| def set_request_context(request_id: str, user_ip: Optional[str] = None): | |
| """Set request context for the current thread.""" | |
| _request_context.request_id = request_id | |
| _request_context.user_ip = user_ip or 'unknown' | |
| def clear_request_context(): | |
| """Clear request context for the current thread.""" | |
| if hasattr(_request_context, 'request_id'): | |
| delattr(_request_context, 'request_id') | |
| if hasattr(_request_context, 'user_ip'): | |
| delattr(_request_context, 'user_ip') | |
| def log_with_extra(logger: logging.Logger, level: str, message: str, **extra_data): | |
| """Log with extra structured data.""" | |
| log_method = getattr(logger, level.lower()) | |
| # Create a custom log record with extra data | |
| if extra_data: | |
| extra_record = {'extra_data': extra_data} | |
| log_method(message, extra=extra_record) | |
| else: | |
| log_method(message) | |
| # Create module-level loggers for common components | |
| app_logger = setup_logger('app') | |
| agent_logger = setup_logger('agents') | |
| retrieval_logger = setup_logger('retrieval') | |
| ingestion_logger = setup_logger('ingestion') | |
| llm_logger = setup_logger('llm') | |
| api_logger = setup_logger('api') | |