106 lines
4.6 KiB
Python
106 lines
4.6 KiB
Python
|
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
|
||
|
|