SXXXXXXX_PyInstallerGUIWrapper/pyinstallerguiwrapper/gui/output_logger.py
2025-06-10 09:09:06 +02:00

128 lines
5.6 KiB
Python

# -*- coding: utf-8 -*-
"""
Manages the logging output to the GUI's scrolled text widget
and to a log file.
"""
import sys
import queue
import threading
import traceback
import tkinter as tk
from tkinter import scrolledtext
import os
import datetime
from typing import Optional, TYPE_CHECKING
if TYPE_CHECKING:
from pyinstallerguiwrapper.gui.main_window import PyInstallerGUI
LOG_FILE_NAME_BASE = "pyinstaller_gui_wrapper"
LOG_FILE_TIMESTAMP_FORMAT = "%Y%m%d_%H%M%S"
LOG_DIRECTORY = "logs"
class OutputLogger:
def __init__(self,
output_text_widget: scrolledtext.ScrolledText,
build_queue: queue.Queue,
parent_window: "PyInstallerGUI"):
self.output_text_widget = output_text_widget
self.build_queue = build_queue
self.parent_window = parent_window
self.log_file_path = self._initialize_log_file()
if self.log_file_path:
self._write_to_log_file(f"--- Log Session Started: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')} ---\n", include_timestamp=False)
def _initialize_log_file(self) -> Optional[str]:
try:
if getattr(sys, 'frozen', False):
base_path = os.path.dirname(sys.executable)
else:
base_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
log_dir_path = os.path.join(base_path, LOG_DIRECTORY)
os.makedirs(log_dir_path, exist_ok=True)
timestamp = datetime.datetime.now().strftime(LOG_FILE_TIMESTAMP_FORMAT)
log_file_name = f"{LOG_FILE_NAME_BASE}_{timestamp}.log"
full_log_path = os.path.join(log_dir_path, log_file_name)
with open(full_log_path, 'a', encoding='utf-8') as f: pass
print(f"[OutputLogger] Logging to file: {full_log_path}")
return full_log_path
except Exception as e:
print(f"[CRITICAL OutputLogger ERROR] Failed to initialize log file: {e}\n{traceback.format_exc()}", file=sys.stderr)
return None
# --- MODIFICA QUI ---
def _write_to_log_file(self, message: str, include_timestamp: bool = True) -> None:
"""Writes a message to the configured log file."""
if not self.log_file_path:
return
# Nonostante il flag, il timestamp è già nel messaggio formattato
# che arriva da log_message. Il flag era per il messaggio di avvio.
# La logica è semplice: scrivi il messaggio così com'è.
try:
with open(self.log_file_path, 'a', encoding='utf-8') as f:
f.write(message)
except Exception as e:
print(f"[CRITICAL OutputLogger FILE WRITE ERROR] {e}\nMessage was: {message.strip()}", file=sys.stderr)
# --- FINE MODIFICA ---
def log_message(self, message: str, level: str = "INFO") -> None:
"""
Logs a message to the GUI's text area and to the log file. Thread-safe.
"""
formatted_message = f"[{level}] {str(message).strip()}\n"
if threading.current_thread() is threading.main_thread():
self._update_output_log_widget(formatted_message)
self._write_to_log_file(formatted_message)
else:
# Da un thread non principale, scriviamo subito sul file (append è thread-safe)
# e mettiamo in coda solo per l'aggiornamento della GUI.
self._write_to_log_file(formatted_message)
self.build_queue.put(("LOG_STREAM", "LOG", formatted_message))
def _update_output_log_widget(self, message: str) -> None:
"""
Internal method to update the ScrolledText widget. Must be called from main thread.
"""
try:
if not self.output_text_widget.winfo_exists(): return
self.output_text_widget.config(state="normal")
self.output_text_widget.insert(tk.END, message)
self.output_text_widget.see(tk.END)
self.output_text_widget.config(state="disabled")
self.output_text_widget.update_idletasks()
except Exception as e:
critical_error_msg = f"[CRITICAL GUI LOG ERROR] Failed to write to GUI log: {e}\nMessage was: {message}{traceback.format_exc()}\n"
print(critical_error_msg, file=sys.stderr)
self._write_to_log_file(critical_error_msg)
def check_build_queue(self) -> None:
"""
Periodically checks the shared build queue for ("LOG_STREAM", "LOG", data) messages.
"""
try:
for _ in range(100):
raw_item = self.build_queue.get_nowait()
if isinstance(raw_item, tuple) and len(raw_item) == 3:
source, msg_type, data = raw_item
if source == "LOG_STREAM" and msg_type == "LOG":
self._update_output_log_widget(str(data))
self.build_queue.task_done()
else:
self.build_queue.put(raw_item)
break
else:
self.log_message(f"Discarding malformed queue item: {raw_item}", "WARNING")
self.build_queue.task_done()
except queue.Empty:
pass
except Exception as e:
print(f"[CRITICAL OutputLogger.check_build_queue ERROR] {e}\n{traceback.format_exc()}", file=sys.stderr)
self._write_to_log_file(f"[CRITICAL] Error in OutputLogger queue processing: {e}\n{traceback.format_exc()}\n")
finally:
if self.parent_window.winfo_exists():
self.parent_window.after(100, self.check_build_queue)