134 lines
5.7 KiB
Python
134 lines
5.7 KiB
Python
# logger_config.py
|
|
import logging
|
|
import tkinter as tk
|
|
from tkinter import scrolledtext
|
|
|
|
# This is the single source of truth for the log file name
|
|
LOG_FILE = "git_svn_sync.log"
|
|
|
|
class TextHandler(logging.Handler):
|
|
"""
|
|
A handler class to redirect log messages to a Tkinter Text widget.
|
|
Ensures thread safety for GUI updates from different threads if necessary,
|
|
although current usage seems single-threaded for logging.
|
|
"""
|
|
|
|
def __init__(self, text_widget):
|
|
"""
|
|
Initializes the TextHandler with a Tkinter ScrolledText widget.
|
|
Args:
|
|
text_widget (tkinter.scrolledtext.ScrolledText): The Tkinter widget.
|
|
"""
|
|
super().__init__()
|
|
# Ensure we store the actual widget
|
|
if not isinstance(text_widget, tk.Text) and not isinstance(text_widget, scrolledtext.ScrolledText):
|
|
raise ValueError("TextHandler requires a valid Tkinter Text or ScrolledText widget.")
|
|
self.text_widget = text_widget
|
|
|
|
def emit(self, record):
|
|
"""
|
|
Formats and directs the log record to the Tkinter text widget.
|
|
This method should be thread-safe if logging can occur from multiple threads.
|
|
Tkinter GUI updates must happen in the main thread.
|
|
"""
|
|
msg = self.format(record) + "\n"
|
|
# Check if the widget exists and is valid before attempting to update it
|
|
if self.text_widget and self.text_widget.winfo_exists():
|
|
try:
|
|
# Schedule the GUI update in the main Tkinter thread
|
|
# Using after(0, ...) ensures it runs in the main event loop
|
|
self.text_widget.after(0, self._insert_text, msg)
|
|
except Exception as e:
|
|
# Fallback or logging for errors during scheduling
|
|
print(f"Error scheduling log message update in Tkinter: {e}")
|
|
# else:
|
|
# Optional: Log to console if the widget is gone
|
|
# print(f"Log Widget gone: {msg.strip()}")
|
|
|
|
|
|
def _insert_text(self, msg):
|
|
"""Helper method to insert text, intended to be called via 'after'."""
|
|
# Double-check widget existence right before insertion
|
|
if self.text_widget and self.text_widget.winfo_exists():
|
|
try:
|
|
# Store current state, modify, insert, restore state
|
|
current_state = self.text_widget['state']
|
|
self.text_widget.config(state=tk.NORMAL)
|
|
self.text_widget.insert(tk.END, msg)
|
|
self.text_widget.config(state=current_state)
|
|
# Autoscroll to the end
|
|
self.text_widget.see(tk.END)
|
|
except tk.TclError as e:
|
|
# Handle cases where the widget might be destroyed between checks
|
|
print(f"TclError inserting log into Text widget: {e}")
|
|
except Exception as e:
|
|
print(f"Unexpected error inserting log into Text widget: {e}")
|
|
|
|
|
|
def setup_logger(log_text_widget=None):
|
|
"""
|
|
Sets up the application logger with optional file and text handlers.
|
|
|
|
Args:
|
|
log_text_widget (tkinter.scrolledtext.ScrolledText, optional):
|
|
The Tkinter ScrolledText widget for live log display. Defaults to None.
|
|
|
|
Returns:
|
|
logging.Logger: The configured logger instance.
|
|
"""
|
|
# Use a specific, consistent name for the application logger
|
|
logger_name = "GitSvnSyncApp"
|
|
logger = logging.getLogger(logger_name)
|
|
|
|
# Set the desired logging level (e.g., INFO, DEBUG)
|
|
logger.setLevel(logging.INFO)
|
|
|
|
# Prevent adding duplicate handlers if called multiple times
|
|
if not logger.handlers:
|
|
# Define a standard log format
|
|
log_formatter = logging.Formatter(
|
|
"%(asctime)s - %(levelname)s - [%(funcName)s] - %(message)s",
|
|
datefmt="%Y-%m-%d %H:%M:%S"
|
|
)
|
|
|
|
# --- File Handler ---
|
|
try:
|
|
# Use the centrally defined LOG_FILE name
|
|
file_handler = logging.FileHandler(LOG_FILE, mode='a', encoding='utf-8') # Append mode
|
|
file_handler.setLevel(logging.INFO) # Log INFO level and above to file
|
|
file_handler.setFormatter(log_formatter)
|
|
logger.addHandler(file_handler)
|
|
print(f"Logging to file: {os.path.abspath(LOG_FILE)}") # Print log file path on setup
|
|
except Exception as e:
|
|
# Fallback to stderr if file logging setup fails
|
|
logging.basicConfig(level=logging.INFO) # Basic config to console
|
|
logger.error(f"Critical: Failed to set up file handler for '{LOG_FILE}': {e}. Logging to console.")
|
|
|
|
# --- Text Widget Handler (Optional) ---
|
|
if log_text_widget:
|
|
try:
|
|
text_handler = TextHandler(log_text_widget)
|
|
# Log DEBUG level and above to the GUI for more detail during runtime
|
|
text_handler.setLevel(logging.DEBUG)
|
|
text_handler.setFormatter(log_formatter)
|
|
logger.addHandler(text_handler)
|
|
logger.info("GUI logging handler configured.")
|
|
except ValueError as e:
|
|
logger.error(f"Error setting up GUI TextHandler: {e}")
|
|
except Exception as e:
|
|
logger.error(f"Unexpected error setting up GUI TextHandler: {e}")
|
|
else:
|
|
logger.info("No GUI log widget provided. Skipping TextHandler setup.")
|
|
|
|
# Initial log message indicating setup is complete
|
|
logger.info("Logger setup complete.")
|
|
# Example debug message (will show in GUI if configured, not in file unless file level is DEBUG)
|
|
logger.debug("Debug logging is active for handlers that support it.")
|
|
|
|
else:
|
|
logger.info("Logger already configured. Skipping setup.")
|
|
|
|
return logger
|
|
|
|
# --- Import os for abspath in setup_logger ---
|
|
import os |