# ========================================================================= # File: app/utils/logger.py # Description: Structured logging setup # ========================================================================= """Logging configuration for the Scam Honeypot System.""" import logging import sys from datetime import datetime from typing import Any from app.config import settings def setup_logging(): """Configure logging for the application.""" level = logging.DEBUG if settings.DEBUG else logging.INFO # Create formatter formatter = logging.Formatter( '%(asctime)s | %(levelname)-8s | %(name)s | %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) # Console handler console_handler = logging.StreamHandler(sys.stdout) console_handler.setFormatter(formatter) # Configure root logger root_logger = logging.getLogger() root_logger.setLevel(level) root_logger.addHandler(console_handler) # Reduce noise from external libraries logging.getLogger("httpx").setLevel(logging.WARNING) logging.getLogger("openai").setLevel(logging.WARNING) logging.getLogger("anthropic").setLevel(logging.WARNING) return root_logger def get_logger(name: str) -> logging.Logger: """Get a logger with the given name.""" return logging.getLogger(name) # Default logger instance for convenience logger = get_logger("app") class AgentLogger: """ Specialized logger for agent activities. Provides structured logging for agent operations. """ def __init__(self, agent_name: str): self.logger = logging.getLogger(f"agent.{agent_name}") self.agent_name = agent_name def _safe_message(self, message: str) -> str: """Strip non-ASCII characters to prevent UnicodeEncodeError on Windows.""" return "".join(c for c in message if ord(c) < 128) def info(self, message: str, **kwargs): """Log info level message.""" clean_msg = self._safe_message(message) extra = self._format_extra(kwargs) self.logger.info(f"{clean_msg} {extra}") def debug(self, message: str, **kwargs): """Log debug level message.""" clean_msg = self._safe_message(message) extra = self._format_extra(kwargs) self.logger.debug(f"{clean_msg} {extra}") def warning(self, message: str, **kwargs): """Log warning level message.""" clean_msg = self._safe_message(message) extra = self._format_extra(kwargs) self.logger.warning(f"{clean_msg} {extra}") def error(self, message: str, **kwargs): """Log error level message.""" clean_msg = self._safe_message(message) extra = self._format_extra(kwargs) self.logger.error(f"{clean_msg} {extra}") def _format_extra(self, kwargs: dict) -> str: """Format extra context for logging with PII masking.""" if not kwargs: return "" # Keys that often contain PII in this system (SOC-Grade Forensic List) PII_KEYS = { 'upi_id', 'phone_number', 'bank_account', 'email', 'pan', 'aadhar', 'upi_ids', 'phone_numbers', 'bank_accounts', 'crypto_addresses', 'names', 'pan_cards', 'aadhar_numbers', 'credit_cards', 'otps' } parts = [] for k, v in kwargs.items(): # Mask sensitive data if k.lower() in PII_KEYS: masked_v = self._mask_pii(v) parts.append(f"{k}={masked_v}") else: parts.append(f"{k}={v}") return f"[{', '.join(parts)}]" def _mask_pii(self, val: Any) -> Any: """Simple masking for PII strings.""" if not isinstance(val, str): if isinstance(val, list): return [self._mask_pii(i) for i in val] return val if len(val) <= 4: return "****" # Keep first 2 and last 2 characters return f"{val[:2]}****{val[-2:]}"