128 lines
5.6 KiB
Python
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) |