keypresser/model/app_logger.py

106 lines
4.6 KiB
Python
Raw Permalink Normal View History

2025-06-19 18:38:55 +00:00
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