# 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