# logging_config.py """ THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. Configures logging for the application. Includes a custom filter (DebugControlFilter) to control DEBUG messages based on flags in the config module, and a setup function (setup_logging) to configure the root logger, handlers, formatters, and apply the filter. """ # Standard library imports import logging import sys # Local application imports from controlpanel import config # For DEBUG flags and logging levels # --- Custom Logging Filter --- class DebugControlFilter(logging.Filter): """ Filters log messages based on level and specific DEBUG flags in `config`. Allows all messages with level > DEBUG. Filters DEBUG messages based on message prefixes and corresponding boolean flags defined in the `config` module. """ def filter(self, record): """ Determines if the specified log record should be processed. Args: record (logging.LogRecord): The log record to check. Returns: bool: True if the record should be logged, False otherwise. """ # Allow levels higher than DEBUG unconditionally if record.levelno > logging.DEBUG: return True # Block levels lower than DEBUG (shouldn't happen if root is DEBUG) if record.levelno < logging.DEBUG: return False # --- Filter DEBUG messages based on prefixes and config flags --- if record.levelno == logging.DEBUG: msg = record.getMessage() # Get the formatted message content # Check Map related prefixes first map_prefixes = ( "[Map", # Cattura [MapTileManager], [MapIntegrationManager], [MapUtils], [MapService], [MapDisplay] # Aggiungi altri prefissi specifici se necessario (es. da helper interni) ) if msg.startswith(map_prefixes): # Se il messaggio inizia con un prefisso mappa E il flag è True, mostralo if config.DEBUG_MAP_DETAILS: return True else: # Altrimenti, se il flag è False, blocca questo messaggio DEBUG mappa return False # Check prefixes against corresponding config flags # App General & Lifecycle if msg.startswith("[App Init]") and config.DEBUG_APP_LIFECYCLE: return True elif msg.startswith("[ImageRecorder") and config.DEBUG_IMAGE_RECORDER: return True # Allow if ImageRecorder prefix and flag is True elif msg.startswith("[App Shutdown]") and config.DEBUG_APP_LIFECYCLE: return True elif msg.startswith("[App Status]") and config.DEBUG_APP_STATUS: return True elif msg.startswith("[AppState") and config.DEBUG_APP_LIFECYCLE: return True # AppState logs elif msg.startswith("[App MainThread") and config.DEBUG_APP_LIFECYCLE: return True # Main thread processing logs # App Callbacks & Processing elif msg.startswith("[App CB") and config.DEBUG_APP_CALLBACKS: if msg.startswith("[App CB MFD Param]") and config.DEBUG_APP_MFD_PARAMS: return True elif not msg.startswith("[App CB MFD Param]"): return True # Allow general CB if not MFD specific elif msg.startswith("[App QProc") and config.DEBUG_APP_QUEUE_PROCESSING: return True elif msg.startswith("[App Proc") and config.DEBUG_APP_IMG_PROCESSING: return True # App Specific Features elif msg.startswith("[App Test") and config.DEBUG_APP_TEST_MODE: return True elif msg.startswith("[App GeoCalc]") and config.DEBUG_APP_GEO_CALC: return True elif msg.startswith("[MFD LUT Update]") and config.DEBUG_APP_MFD_PARAMS: return True elif msg.startswith("[App Trigger") and config.DEBUG_APP_TRIGGER: return True # Map related App logs elif msg.startswith("[App Init Map") and config.DEBUG_APP_LIFECYCLE: return True elif msg.startswith("[App Map Update]") and config.DEBUG_APP_LIFECYCLE: return True # Receiver Related elif msg.startswith("[Receiver Loop]") and config.DEBUG_RECEIVER_LOOP: return True elif msg.startswith("[Receiver ACK]") and config.DEBUG_RECEIVER_ACK: return True elif msg.startswith("[Receiver Cleanup]") and config.DEBUG_RECEIVER_CLEANUP: return True elif "Rcvd: Key=" in msg and config.DEBUG_RECEIVER_PACKETS: return True # Raw packet header elif ( msg.startswith("[Worker") or msg.startswith("[Receiver Reassembly]") or "Reassembling" in msg or "Identified" in msg or "reassemble" in msg.lower() or "New TX" in msg or "All frags" in msg or "Cleaned Key=" in msg or "Submit" in msg or "pixel data offset" in msg or "cleanup" in msg.lower() ) and config.DEBUG_RECEIVER_REASSEMBLY: return True # Reassembly logs elif ( msg.startswith("[Geo extract]") or "GeoInfo:" in msg or "Invalid GeoInfo" in msg or "Invalid SAR meta" in msg or "Invalid MFD meta" in msg or "GeoData extraction" in msg ) and config.DEBUG_RECEIVER_GEO: return True # Geo extraction logs # Display/UI Related elif msg.startswith("[DisplayMgr]") and config.DEBUG_DISPLAY_MANAGER: return True elif msg.startswith("[MapDisplay]") and config.DEBUG_DISPLAY_MANAGER: return True elif msg.startswith("[UI Setup]") and config.DEBUG_APP_LIFECYCLE: return True # UI creation logs elif msg.startswith("[UI Update]") and config.DEBUG_APP_CALLBACKS: return True # General UI updates elif msg.startswith("[UI Status]") and config.DEBUG_APP_STATUS: return True # Status bar updates # Utility Modules elif msg.startswith("[Utils") and config.DEBUG_UTILS: return True elif msg.startswith("[Network") and config.DEBUG_NETWORK: return True elif msg.startswith("[ImageProcessing") and config.DEBUG_IMAGE_PROCESSING: return True elif msg.startswith("[MapUtils") and config.DEBUG_UTILS: return True elif msg.startswith("[MapTileManager") and config.DEBUG_UTILS: return True elif msg.startswith("[MapService") and config.DEBUG_UTILS: return True # Default: Block DEBUG message if no specific flag allows it return False # Should not be reached if handler level > DEBUG, but return False defensively return False # --- Logging Setup Function --- def setup_logging(): """Configures the root logger for the application using DebugControlFilter.""" log_prefix = "[Log Setup]" # Prefix for this function's messages # Check if logging is already configured (simple check based on handlers) root_logger = logging.getLogger() if root_logger.hasHandlers(): # Check if our specific handler/filter is already present has_our_filter = any( isinstance(f, DebugControlFilter) for h in root_logger.handlers for f in h.filters ) if has_our_filter: logging.debug( f"{log_prefix} Logging seems already configured with DebugControlFilter. Skipping setup." ) return # Avoid re-configuring if already done # Include thread name in the log format log_format = "%(asctime)s - %(levelname)s - [%(threadName)s] - %(filename)s:%(lineno)d - %(message)s" log_formatter = logging.Formatter(log_format) # Re-configure root logger if needed (e.g., level changed) # Ensure root level is low enough to allow filter to work on DEBUG messages root_logger.setLevel(config.LOG_ROOT_LEVEL) # Remove existing handlers before adding new ones to prevent duplication # (More robust reconfiguration) # Use print here as logging might be in an intermediate state if root_logger.handlers: print( f"{log_prefix} Removing existing logging handlers before reconfiguration." ) for handler in root_logger.handlers[:]: # Iterate over a copy root_logger.removeHandler(handler) handler.close() # Close the handler properly # Configure console handler # Use stderr by default for logs? Or keep stdout? Default is stderr for StreamHandler. console_handler = logging.StreamHandler( sys.stdout ) # Explicitly use stdout for visibility? Or stderr? console_handler.setLevel(config.LOG_HANDLER_LEVEL) # Use level from config console_handler.setFormatter(log_formatter) # Add custom filter debug_filter = DebugControlFilter() console_handler.addFilter(debug_filter) # Add the configured handler to the root logger root_logger.addHandler(console_handler) # Use INFO level for confirmation messages after setup logging.info(f"{log_prefix} Console log handler added and configured.") logging.info( f"{log_prefix} Logging configured: Root Level={logging.getLevelName(root_logger.level)}, " f"Handler Level={logging.getLevelName(console_handler.level)}" ) # Log status of all current debug flags log_flags_status = ( f"{log_prefix} Debug Flags: Rcv[" f"Pkt:{config.DEBUG_RECEIVER_PACKETS}," f"Rsm:{config.DEBUG_RECEIVER_REASSEMBLY}," f"Geo:{config.DEBUG_RECEIVER_GEO}," f"Loop:{config.DEBUG_RECEIVER_LOOP}," f"Ack:{config.DEBUG_RECEIVER_ACK}," f"Cln:{config.DEBUG_RECEIVER_CLEANUP}], " f"App[" f"Life:{config.DEBUG_APP_LIFECYCLE}," f"CB:{config.DEBUG_APP_CALLBACKS}," f"Q:{config.DEBUG_APP_QUEUE_PROCESSING}," f"Proc:{config.DEBUG_APP_IMG_PROCESSING}," f"Test:{config.DEBUG_APP_TEST_MODE}," f"GeoCalc:{config.DEBUG_APP_GEO_CALC}," f"MFDParam:{config.DEBUG_APP_MFD_PARAMS}," f"Stat:{config.DEBUG_APP_STATUS}," f"Trig:{config.DEBUG_APP_TRIGGER}], " f"Disp:{config.DEBUG_DISPLAY_MANAGER}, " f"Map:{config.DEBUG_MAP_DETAILS}, " f"Util:{config.DEBUG_UTILS}, " f"Net:{config.DEBUG_NETWORK}, " f"ImgProc:{config.DEBUG_IMAGE_PROCESSING}, " f"ImgRec:{config.DEBUG_IMAGE_RECORDER}" "]" ) logging.info(log_flags_status)