""" Centralized logging configuration for AgentPress. This module provides a unified logging interface with: - Structured JSON logging for better parsing - Log levels for different environments - Correlation IDs for request tracing - Contextual information for debugging """ import logging import json import sys import os from datetime import datetime from typing import Any, Dict, Optional from contextvars import ContextVar from functools import wraps import traceback from logging.handlers import RotatingFileHandler from utils.config import config, EnvMode # Context variable for request correlation ID request_id: ContextVar[str] = ContextVar('request_id', default='') class JSONFormatter(logging.Formatter): """Custom JSON formatter for structured logging.""" def format(self, record: logging.LogRecord) -> str: """Format log record as JSON with contextual information.""" log_data = { 'timestamp': datetime.utcnow().isoformat(), 'level': record.levelname, 'message': record.getMessage(), 'module': record.module, 'function': record.funcName, 'line': record.lineno, 'request_id': request_id.get(), 'thread_id': getattr(record, 'thread_id', None), 'correlation_id': getattr(record, 'correlation_id', None) } # Add extra fields if present if hasattr(record, 'extra'): log_data.update(record.extra) # Add exception info if present if record.exc_info: log_data['exception'] = { 'type': str(record.exc_info[0].__name__), 'message': str(record.exc_info[1]), 'traceback': traceback.format_exception(*record.exc_info) } return json.dumps(log_data) def setup_logger(name: str = 'agentpress') -> logging.Logger: """ Set up a centralized logger with both file and console handlers. Args: name: The name of the logger Returns: logging.Logger: Configured logger instance """ logger = logging.getLogger(name) # Set console logging level based on environment if config.ENV_MODE == EnvMode.PRODUCTION: logger.setLevel(logging.WARNING) else: logger.setLevel(logging.INFO) # Create logs directory if it doesn't exist log_dir = 'logs' if not os.path.exists(log_dir): os.makedirs(log_dir) # File handler with rotation log_file = os.path.join(log_dir, f'{name}_{datetime.now().strftime("%Y%m%d")}.log') file_handler = RotatingFileHandler( log_file, maxBytes=10*1024*1024, # 10MB backupCount=5, encoding='utf-8' ) file_handler.setLevel(logging.DEBUG) # Console handler console_handler = logging.StreamHandler(sys.stdout) # Set console logging level based on environment if config.ENV_MODE == EnvMode.PRODUCTION: console_handler.setLevel(logging.INFO) else: console_handler.setLevel(logging.DEBUG) # Create formatters file_formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s' ) console_formatter = logging.Formatter( '%(asctime)s - %(levelname)s - %(message)s' ) # Set formatters file_handler.setFormatter(file_formatter) console_handler.setFormatter(console_formatter) # Add handlers to logger logger.addHandler(file_handler) logger.addHandler(console_handler) return logger # Create default logger instance logger = setup_logger()