""" Structured logging setup for Calejo Control Adapter. """ import structlog import logging import sys import uuid from datetime import datetime from typing import Dict, Any, Optional from config.settings import settings def setup_logging(): """Setup structured logging with JSON formatting.""" # Configure standard logging logging.basicConfig( format="%(message)s", stream=sys.stdout, level=getattr(logging, settings.log_level) ) # Configure structlog processors = [ structlog.stdlib.filter_by_level, structlog.stdlib.add_logger_name, structlog.stdlib.add_log_level, structlog.stdlib.PositionalArgumentsFormatter(), structlog.processors.TimeStamper(fmt="iso"), structlog.processors.StackInfoRenderer(), structlog.processors.format_exc_info, add_correlation_id, add_application_info, ] if settings.log_format == "json": processors.append(structlog.processors.JSONRenderer()) else: processors.append(structlog.dev.ConsoleRenderer()) structlog.configure( processors=processors, wrapper_class=structlog.stdlib.BoundLogger, logger_factory=structlog.stdlib.LoggerFactory(), cache_logger_on_first_use=True, ) logger = structlog.get_logger() logger.info( "logging_configured", log_level=settings.log_level, log_format=settings.log_format, environment=settings.environment ) return logger def add_correlation_id(_, __, event_dict: Dict[str, Any]) -> Dict[str, Any]: """Add correlation ID to log entries for request tracing.""" if 'correlation_id' not in event_dict: event_dict['correlation_id'] = str(uuid.uuid4()) return event_dict def add_application_info(_, __, event_dict: Dict[str, Any]) -> Dict[str, Any]: """Add application information to log entries.""" event_dict['app_name'] = settings.app_name event_dict['app_version'] = settings.app_version event_dict['environment'] = settings.environment return event_dict class AuditLogger: """Audit logger for compliance requirements.""" def __init__(self, db_client): self.db_client = db_client self.logger = structlog.get_logger(__name__) def log( self, event_type: str, severity: str, station_id: Optional[str] = None, pump_id: Optional[str] = None, user_id: Optional[str] = None, ip_address: Optional[str] = None, protocol: Optional[str] = None, action: Optional[str] = None, resource: Optional[str] = None, result: Optional[str] = None, event_data: Optional[Dict[str, Any]] = None ): """Log an audit event to both structured logs and database.""" # Log to structured logs self.logger.info( "audit_event", event_type=event_type, severity=severity, station_id=station_id, pump_id=pump_id, user_id=user_id, ip_address=ip_address, protocol=protocol, action=action, resource=resource, result=result, event_data=event_data ) # Log to database if audit logging is enabled if settings.audit_log_enabled: try: query = """ INSERT INTO audit_log (event_type, severity, station_id, pump_id, user_id, ip_address, protocol, action, resource, result, event_data) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) """ self.db_client.execute( query, ( event_type, severity, station_id, pump_id, user_id, ip_address, protocol, action, resource, result, event_data ) ) except Exception as e: self.logger.error("audit_log_database_failed", error=str(e)) # Global logger instance logger = setup_logging()