import logging import queue import sys import os import datetime import configparser # --- Global Message Queue for GUI Communication --- # All log messages and AHK output pass through this queue to the View. message_queue = queue.Queue() # --- Custom QueueHandler for UI messages --- class QueueHandler(logging.Handler): """ Custom logging handler to push *only the raw message* to a queue, allowing the GUI to display it directly without additional formatting. """ def __init__(self, message_queue): super().__init__() self.message_queue = message_queue def emit(self, record): # Use record.getMessage() to get the log message without formatters applied self.message_queue.put(record.getMessage()) # --- Configuration Constants for Logging --- LOG_FILENAME_BASE = "JarvisKeyPressUtility" CONFIG_FILE_NAME = "config.ini" def setup_logging_from_config(): """ Sets up logging handlers (console and file) based on settings in config.ini. Creates a default config.ini if it doesn't exist. This function should be called once at application startup. """ root_logger = logging.getLogger() # Clear existing handlers to prevent duplicates if function is called multiple times if root_logger.handlers: for handler in list(root_logger.handlers): root_logger.removeHandler(handler) config = configparser.ConfigParser() # Determine the execution directory for the config and log files if getattr(sys, 'frozen', False): # Running as a PyInstaller executable execution_dir = os.path.dirname(sys.executable) else: # Running as a Python script # When called from main.py, os.path.abspath(__file__) points to model/app_logger.py. # Need to go up two levels to reach project root where config.ini is expected. execution_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) config_file_path = os.path.join(execution_dir, CONFIG_FILE_NAME) # Create default config.ini if it doesn't exist if not os.path.exists(config_file_path): config['Logging'] = { 'file_log_level': 'DEBUG', # Default to DEBUG for file 'console_log_level': 'INFO' # Default to INFO for console } try: with open(config_file_path, 'w') as f: config.write(f) print(f"Created default '{CONFIG_FILE_NAME}' at '{config_file_path}'") except IOError as e: print(f"Error creating default config.ini at {config_file_path}: {e}") # Fallback to default levels if config file cannot be created file_log_level = logging.DEBUG console_log_level = logging.INFO else: config.read(config_file_path) # Get log levels from config, default to INFO if not found or invalid file_log_level_str = config.get('Logging', 'file_log_level', fallback='DEBUG').upper() console_log_level_str = config.get('Logging', 'console_log_level', fallback='INFO').upper() file_log_level = getattr(logging, file_log_level_str, logging.INFO) console_log_level = getattr(logging, console_log_level_str, logging.INFO) # Set the root logger level to the lowest (most verbose) level needed by any handler root_logger.setLevel(min(file_log_level, console_log_level, logging.WARNING)) # 1. Console Handler: Outputs to stdout console_handler = logging.StreamHandler(sys.stdout) console_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s') console_handler.setFormatter(console_formatter) console_handler.setLevel(console_log_level) # Set level for console output root_logger.addHandler(console_handler) # 2. File Handler: Outputs to a log file timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") log_file_path = os.path.join(execution_dir, f"{LOG_FILENAME_BASE}_{timestamp}.log") file_handler = logging.FileHandler(log_file_path, encoding='utf-8') file_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(funcName)s - %(message)s') file_handler.setFormatter(file_formatter) file_handler.setLevel(file_log_level) # Set level for file output (e.g., DEBUG for all) root_logger.addHandler(file_handler) # 3. UI Queue Handler (for messages sent to Tkinter GUI) ui_queue_handler = QueueHandler(message_queue) ui_queue_handler.setLevel(logging.WARNING) # UI usually wants WARNING or higher messages by default root_logger.addHandler(ui_queue_handler) return log_file_path # Return path for initial log message in main