""" Professional logging utility for the Ebook Coupon Management System Provides structured logging with proper formatting and log levels. """ import logging import logging.handlers import os import sys from datetime import datetime from typing import Optional, Any, Dict import json class SafeJSONEncoder(json.JSONEncoder): """Custom JSON encoder that handles non-serializable objects safely""" def default(self, obj): """Handle non-serializable objects by converting them to strings""" if hasattr(obj, '__dict__'): return str(obj) elif hasattr(obj, '__str__'): return str(obj) else: return f"<{type(obj).__name__} object>" class StructuredFormatter(logging.Formatter): """Custom formatter for structured logging""" def format(self, record: logging.LogRecord) -> str: """Format log record with structured data""" log_entry = { "timestamp": datetime.utcnow().isoformat(), "level": record.levelname, "logger": record.name, "message": record.getMessage(), "module": record.module, "function": record.funcName, "line": record.lineno } # Add extra fields if present if hasattr(record, 'request_id'): log_entry['request_id'] = record.request_id if hasattr(record, 'method'): log_entry['method'] = record.method if hasattr(record, 'path'): log_entry['path'] = record.path if hasattr(record, 'status_code'): log_entry['status_code'] = record.status_code if hasattr(record, 'process_time'): log_entry['process_time'] = record.process_time if hasattr(record, 'client_ip'): log_entry['client_ip'] = record.client_ip if hasattr(record, 'user_agent'): log_entry['user_agent'] = record.user_agent if hasattr(record, 'error'): log_entry['error'] = record.error if hasattr(record, 'exception_type'): log_entry['exception_type'] = record.exception_type if hasattr(record, 'exception_message'): log_entry['exception_message'] = record.exception_message if hasattr(record, 'errors'): # Handle errors list safely try: if isinstance(record.errors, list): log_entry['errors'] = [str(error) if not isinstance(error, (dict, str, int, float, bool)) else error for error in record.errors] else: log_entry['errors'] = str(record.errors) except Exception: log_entry['errors'] = str(record.errors) if hasattr(record, 'app_name'): log_entry['app_name'] = record.app_name if hasattr(record, 'version'): log_entry['version'] = record.version if hasattr(record, 'environment'): log_entry['environment'] = record.environment if hasattr(record, 'debug'): log_entry['debug'] = record.debug # Add exception info if present if record.exc_info: log_entry['exception'] = self.formatException(record.exc_info) return json.dumps(log_entry, ensure_ascii=False, cls=SafeJSONEncoder) def setup_logger(name: str, level: Optional[str] = None) -> logging.Logger: """ Setup a logger with proper configuration Args: name: Logger name level: Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL) Returns: Configured logger instance """ # Get log level from environment or use default log_level = level or os.getenv("LOG_LEVEL", "INFO").upper() # Create logger logger = logging.getLogger(name) logger.setLevel(getattr(logging, log_level)) # Avoid duplicate handlers if logger.handlers: return logger # Create formatters structured_formatter = StructuredFormatter() console_formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) # Console handler console_handler = logging.StreamHandler(sys.stdout) console_handler.setLevel(logging.DEBUG) console_handler.setFormatter(console_formatter) logger.addHandler(console_handler) # File handler for structured logs log_dir = "logs" os.makedirs(log_dir, exist_ok=True) file_handler = logging.handlers.RotatingFileHandler( os.path.join(log_dir, "app.log"), maxBytes=10*1024*1024, # 10MB backupCount=5 ) file_handler.setLevel(logging.INFO) file_handler.setFormatter(structured_formatter) logger.addHandler(file_handler) # Error file handler error_handler = logging.handlers.RotatingFileHandler( os.path.join(log_dir, "error.log"), maxBytes=10*1024*1024, # 10MB backupCount=5 ) error_handler.setLevel(logging.ERROR) error_handler.setFormatter(structured_formatter) logger.addHandler(error_handler) return logger def get_logger(name: str) -> logging.Logger: """ Get a logger instance Args: name: Logger name Returns: Logger instance """ return logging.getLogger(name) # Create default logger default_logger = setup_logger("ebook_coupon_system")