add thread for map, add queue for loggin system
This commit is contained in:
parent
574a8bf7e0
commit
5842e8c37c
@ -716,34 +716,47 @@ class AppController:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def on_application_exit(self):
|
def on_application_exit(self):
|
||||||
module_logger.info(
|
module_logger.info("Controller: Application exit requested. Cleaning up resources.")
|
||||||
"Controller: Application exit requested. Cleaning up resources."
|
|
||||||
)
|
# MODIFIED: Added shutdown for MapCanvasManager's worker thread.
|
||||||
is_adapter_considered_running = (
|
# WHY: To ensure the map rendering thread is properly terminated before the application exits.
|
||||||
self.live_adapter_thread and self.live_adapter_thread.is_alive()
|
# WHERE: At the beginning of the application exit process.
|
||||||
) or self.is_live_monitoring_active
|
# HOW: Checking for the existence of main_window, map_manager_instance,
|
||||||
if is_adapter_considered_running:
|
# and the shutdown_worker method, then calling it within a try-except block.
|
||||||
module_logger.debug(
|
if self.main_window and \
|
||||||
"Controller: Live monitoring/adapter active during app exit, stopping it."
|
hasattr(self.main_window, "map_manager_instance") and \
|
||||||
)
|
self.main_window.map_manager_instance is not None:
|
||||||
self.stop_live_monitoring(from_error=False)
|
map_manager = self.main_window.map_manager_instance
|
||||||
|
if hasattr(map_manager, "shutdown_worker") and callable(map_manager.shutdown_worker):
|
||||||
|
module_logger.debug("Controller: Requesting MapCanvasManager to shutdown its worker.")
|
||||||
|
try:
|
||||||
|
map_manager.shutdown_worker()
|
||||||
|
module_logger.info("Controller: MapCanvasManager worker shutdown requested.")
|
||||||
|
except Exception as e_map_shutdown:
|
||||||
|
module_logger.error(
|
||||||
|
f"Controller: Error during MapCanvasManager worker shutdown: {e_map_shutdown}",
|
||||||
|
exc_info=True
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
module_logger.debug("Controller: MapCanvasManager instance found, but no 'shutdown_worker' method.")
|
||||||
else:
|
else:
|
||||||
module_logger.debug(
|
module_logger.debug("Controller: No MapCanvasManager instance found or main window not set; skipping map worker shutdown.")
|
||||||
"Controller: Live monitoring/adapter not active or already stopped during app exit."
|
|
||||||
)
|
is_adapter_considered_running = (self.live_adapter_thread and self.live_adapter_thread.is_alive()) or self.is_live_monitoring_active
|
||||||
|
if is_adapter_considered_running:
|
||||||
|
module_logger.debug("Controller: Live monitoring/adapter active during app exit, stopping it.")
|
||||||
|
self.stop_live_monitoring(from_error=False) # This will join the adapter thread
|
||||||
|
else:
|
||||||
|
module_logger.debug("Controller: Live monitoring/adapter not active or already stopped during app exit.")
|
||||||
|
|
||||||
if self.data_storage:
|
if self.data_storage:
|
||||||
module_logger.debug(
|
module_logger.debug("Controller: Closing DataStorage connection during app exit.")
|
||||||
"Controller: Closing DataStorage connection during app exit."
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
self.data_storage.close_connection()
|
self.data_storage.close_connection()
|
||||||
except Exception as e_db_close:
|
except Exception as e_db_close:
|
||||||
module_logger.error(
|
module_logger.error(f"Error closing DataStorage: {e_db_close}", exc_info=True)
|
||||||
f"Error closing DataStorage: {e_db_close}", exc_info=True
|
|
||||||
)
|
|
||||||
finally:
|
finally:
|
||||||
self.data_storage = None
|
self.data_storage = None # Ensure it's cleared
|
||||||
module_logger.info("Controller: Cleanup on application exit finished.")
|
module_logger.info("Controller: Cleanup on application exit finished.")
|
||||||
|
|
||||||
def start_history_monitoring(self):
|
def start_history_monitoring(self):
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import logging # Import per i livelli di logging (es. logging.ERROR)
|
|||||||
|
|
||||||
# Relative imports
|
# Relative imports
|
||||||
from ..data import config as app_config # Standardized alias
|
from ..data import config as app_config # Standardized alias
|
||||||
from ..utils.logger import get_logger, setup_logging, shutdown_gui_logging
|
from ..utils.logger import get_logger, setup_logging, shutdown_logging_system
|
||||||
from ..data.common_models import CanonicalFlightState
|
from ..data.common_models import CanonicalFlightState
|
||||||
|
|
||||||
# Importa le costanti di stato GUI dal modulo utils centralizzato
|
# Importa le costanti di stato GUI dal modulo utils centralizzato
|
||||||
@ -823,14 +823,15 @@ class MainWindow:
|
|||||||
f"Error during controller.on_application_exit: {e}",
|
f"Error during controller.on_application_exit: {e}",
|
||||||
exc_info=True,
|
exc_info=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
module_logger.info("Shutting down GUI logging.")
|
module_logger.info("Shutting down logging system.") # Messaggio aggiornato
|
||||||
try:
|
try:
|
||||||
shutdown_gui_logging() # Assicurati che questa funzione sia robusta
|
# MODIFIED: Changed shutdown_gui_logging() to shutdown_logging_system()
|
||||||
except Exception as e: # Logga eventuali errori, ma non fermare la chiusura
|
# WHY: The function name was changed in the logger.py module.
|
||||||
module_logger.error(
|
# HOW: Updated the function call.
|
||||||
f"Error during shutdown_gui_logging: {e}", exc_info=True
|
shutdown_logging_system() # Assicurati che questa funzione sia robusta
|
||||||
)
|
except Exception as e:
|
||||||
|
module_logger.error(f"Error during shutdown_logging_system: {e}", exc_info=True)
|
||||||
|
|
||||||
if hasattr(self, "root") and self.root.winfo_exists():
|
if hasattr(self, "root") and self.root.winfo_exists():
|
||||||
try:
|
try:
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -51,18 +51,20 @@ DEFAULT_MAX_ZOOM_FALLBACK: int = 19
|
|||||||
# Default color for placeholder tile images when a tile cannot be loaded.
|
# Default color for placeholder tile images when a tile cannot be loaded.
|
||||||
DEFAULT_PLACEHOLDER_COLOR_RGB: Tuple[int, int, int] = (220, 220, 220) # Light grey
|
DEFAULT_PLACEHOLDER_COLOR_RGB: Tuple[int, int, int] = (220, 220, 220) # Light grey
|
||||||
|
|
||||||
|
# MODIFIED: Added constant for Tkinter canvas background color for placeholders.
|
||||||
|
# WHY: Needed by MapCanvasManager to set the canvas background when displaying placeholder text.
|
||||||
|
# HOW: Added the new constant definition.
|
||||||
|
DEFAULT_PLACEHOLDER_COLOR_RGB_TK: str = "gray85" # Tkinter color string for canvas background
|
||||||
|
|
||||||
|
|
||||||
# --- Map Information Panel Formatting ---
|
# --- Map Information Panel Formatting ---
|
||||||
|
|
||||||
# Number of decimal places to display for coordinates in the info panel.
|
# Number of decimal places to display for coordinates in the info panel.
|
||||||
COORDINATE_DECIMAL_PLACES: int = 5
|
COORDINATE_DECIMAL_PLACES: int = 5
|
||||||
# MODIFIED: Add constant for decimal places for map size in km.
|
|
||||||
# WHY: Centralize formatting constant for map info panel.
|
|
||||||
# HOW: Added the constant.
|
|
||||||
MAP_SIZE_KM_DECIMAL_PLACES: int = 1 # Number of decimal places for map size in km.
|
MAP_SIZE_KM_DECIMAL_PLACES: int = 1 # Number of decimal places for map size in km.
|
||||||
|
|
||||||
|
|
||||||
# Define standard degree, minute, second symbols for DMS formatting
|
# Define standard degree, minute, second symbols for DMS formatting
|
||||||
DMS_DEGREE_SYMBOL: str = "°"
|
DMS_DEGREE_SYMBOL: str = "°"
|
||||||
DMS_MINUTE_SYMBOL: str = "'"
|
DMS_MINUTE_SYMBOL: str = "'"
|
||||||
DMS_SECOND_SYMBOL: str = "''"
|
DMS_SECOND_SYMBOL: str = "''"
|
||||||
@ -1,295 +1,235 @@
|
|||||||
# FlightMonitor/utils/logger.py
|
# FlightMonitor/utils/logger.py
|
||||||
import logging
|
import logging
|
||||||
|
import logging.handlers # For RotatingFileHandler
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter.scrolledtext import ScrolledText
|
from tkinter.scrolledtext import ScrolledText
|
||||||
from queue import Queue, Empty as QueueEmpty
|
from queue import Queue, Empty as QueueEmpty # Renamed for clarity
|
||||||
from typing import Optional, Dict, Any
|
from typing import Optional, Dict, Any
|
||||||
|
|
||||||
|
# --- Module-level globals for the new centralized logging queue system ---
|
||||||
|
_global_log_queue: Optional[Queue[logging.LogRecord]] = None
|
||||||
|
_actual_console_handler: Optional[logging.StreamHandler] = None
|
||||||
|
_actual_file_handler: Optional[logging.handlers.RotatingFileHandler] = None
|
||||||
|
_actual_tkinter_handler: Optional["TkinterTextHandler"] = None # Forward declaration
|
||||||
|
|
||||||
|
_log_processor_after_id: Optional[str] = None
|
||||||
|
_logging_system_active: bool = False
|
||||||
|
_tk_root_instance_for_processing: Optional[tk.Tk] = None
|
||||||
|
_base_formatter: Optional[logging.Formatter] = None # Store the main formatter
|
||||||
|
|
||||||
|
# Interval for polling the global log queue by the GUI thread processor
|
||||||
|
GLOBAL_LOG_QUEUE_POLL_INTERVAL_MS = 50
|
||||||
|
|
||||||
|
|
||||||
class TkinterTextHandler(logging.Handler):
|
class TkinterTextHandler(logging.Handler):
|
||||||
"""
|
"""
|
||||||
A logging handler that directs log messages to a Tkinter Text widget
|
A logging handler that directs log messages to a Tkinter Text widget.
|
||||||
in a thread-safe manner using an internal queue and root.after().
|
This handler itself is now called by _process_global_log_queue,
|
||||||
|
which runs in the GUI thread. Its internal queue is for batching updates
|
||||||
|
to the Text widget if needed, or could be simplified later.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
text_widget: tk.Text,
|
text_widget: tk.Text, # ScrolledText is a tk.Text
|
||||||
root_tk_instance: tk.Tk,
|
|
||||||
level_colors: Dict[int, str],
|
level_colors: Dict[int, str],
|
||||||
queue_poll_interval_ms: int,
|
# root_tk_instance and queue_poll_interval_ms for its own internal queue might be redundant
|
||||||
|
# if _process_global_log_queue handles batching, but kept for now.
|
||||||
|
# For now, this handler will still use its own internal queue and after loop for safety.
|
||||||
|
root_tk_instance_for_widget_update: tk.Tk,
|
||||||
|
internal_poll_interval_ms: int = 100
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.text_widget = text_widget
|
self.text_widget = text_widget
|
||||||
self.root_tk_instance = root_tk_instance
|
|
||||||
self.log_queue = Queue()
|
|
||||||
self.level_colors = level_colors
|
self.level_colors = level_colors
|
||||||
self.queue_poll_interval_ms = queue_poll_interval_ms
|
self._is_active = True # Internal active state for this specific handler
|
||||||
self._after_id_log_processor: Optional[str] = None
|
self._widget_update_queue = Queue()
|
||||||
self._is_active = True
|
self._root_for_widget_update = root_tk_instance_for_widget_update
|
||||||
|
self._internal_poll_interval_ms = internal_poll_interval_ms
|
||||||
|
self._internal_after_id: Optional[str] = None
|
||||||
|
|
||||||
if not (
|
if not (
|
||||||
self.text_widget
|
self.text_widget
|
||||||
and hasattr(self.text_widget, "winfo_exists")
|
and hasattr(self.text_widget, "winfo_exists")
|
||||||
and self.text_widget.winfo_exists()
|
and self.text_widget.winfo_exists()
|
||||||
):
|
):
|
||||||
print(
|
print("ERROR: TkinterTextHandler initialized with an invalid or non-existent text_widget.", flush=True)
|
||||||
"Warning: TkinterTextHandler initialized with an invalid or non-existent text_widget.",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
self._is_active = False
|
self._is_active = False
|
||||||
return
|
return
|
||||||
|
|
||||||
if not (
|
if not (
|
||||||
self.root_tk_instance
|
self._root_for_widget_update
|
||||||
and hasattr(self.root_tk_instance, "winfo_exists")
|
and hasattr(self._root_for_widget_update, "winfo_exists")
|
||||||
and self.root_tk_instance.winfo_exists()
|
and self._root_for_widget_update.winfo_exists()
|
||||||
):
|
):
|
||||||
print(
|
print("ERROR: TkinterTextHandler initialized with an invalid root for widget update.", flush=True)
|
||||||
"Warning: TkinterTextHandler initialized with an invalid or non-existent root_tk_instance.",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
self._is_active = False
|
self._is_active = False
|
||||||
return
|
return
|
||||||
|
|
||||||
if self._is_active:
|
if self._is_active:
|
||||||
for level, color_value in self.level_colors.items():
|
self._configure_tags()
|
||||||
level_name = logging.getLevelName(level)
|
self._process_widget_update_queue() # Start its own update loop
|
||||||
if color_value:
|
|
||||||
try:
|
|
||||||
self.text_widget.tag_config(level_name, foreground=color_value)
|
|
||||||
except tk.TclError:
|
|
||||||
print(
|
|
||||||
f"Warning: Could not configure tag for {level_name} during TkinterTextHandler init.",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
pass
|
|
||||||
|
|
||||||
if self._is_active:
|
def _configure_tags(self):
|
||||||
self._process_log_queue()
|
if not self._is_active or not self.text_widget.winfo_exists(): return
|
||||||
|
for level, color_value in self.level_colors.items():
|
||||||
|
level_name = logging.getLevelName(level)
|
||||||
|
if color_value:
|
||||||
|
try:
|
||||||
|
self.text_widget.tag_config(level_name, foreground=color_value)
|
||||||
|
except tk.TclError:
|
||||||
|
print(f"Warning: Could not configure tag for {level_name} in TkinterTextHandler.", flush=True)
|
||||||
|
|
||||||
def emit(self, record: logging.LogRecord):
|
def emit(self, record: logging.LogRecord):
|
||||||
if not self._is_active:
|
# This emit is called by _process_global_log_queue (already in GUI thread)
|
||||||
|
if not self._is_active or not self.text_widget.winfo_exists():
|
||||||
|
# This case should ideally not happen if setup is correct
|
||||||
|
print(f"DEBUG: TkinterTextHandler.emit called but inactive or widget gone. Record: {record.getMessage()}", flush=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not (
|
|
||||||
hasattr(self.text_widget, "winfo_exists")
|
|
||||||
and self.text_widget.winfo_exists()
|
|
||||||
and hasattr(self.root_tk_instance, "winfo_exists")
|
|
||||||
and self.root_tk_instance.winfo_exists()
|
|
||||||
):
|
|
||||||
print(
|
|
||||||
"Warning: TkinterTextHandler.emit: Widget or root instance does not exist. Deactivating handler.",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
self._is_active = False
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
msg = self.format(record)
|
msg = self.format(record) # Format the record using the formatter set on this handler
|
||||||
level_name = record.levelname
|
level_name = record.levelname
|
||||||
self.log_queue.put_nowait((level_name, msg))
|
# Put in its own queue for batched widget updates
|
||||||
|
self._widget_update_queue.put_nowait((level_name, msg))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error in TkinterTextHandler.emit before queueing: {e}", flush=True)
|
print(f"Error in TkinterTextHandler.emit (to internal queue): {e}", flush=True)
|
||||||
|
|
||||||
def _process_log_queue(self):
|
def _process_widget_update_queue(self):
|
||||||
if not self._is_active:
|
if not self._is_active or \
|
||||||
if (
|
not self.text_widget.winfo_exists() or \
|
||||||
self._after_id_log_processor
|
not self._root_for_widget_update.winfo_exists():
|
||||||
and hasattr(self.root_tk_instance, "winfo_exists")
|
|
||||||
and self.root_tk_instance.winfo_exists()
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
self.root_tk_instance.after_cancel(self._after_id_log_processor)
|
|
||||||
except tk.TclError:
|
|
||||||
pass
|
|
||||||
except Exception as e:
|
|
||||||
print(
|
|
||||||
f"Error cancelling after_id in _process_log_queue (handler inactive): {e}",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
self._after_id_log_processor = None
|
|
||||||
return
|
|
||||||
|
|
||||||
if not (
|
|
||||||
hasattr(self.text_widget, "winfo_exists")
|
|
||||||
and self.text_widget.winfo_exists()
|
|
||||||
and hasattr(self.root_tk_instance, "winfo_exists")
|
|
||||||
and self.root_tk_instance.winfo_exists()
|
|
||||||
):
|
|
||||||
print(
|
|
||||||
"Debug: TkinterTextHandler._process_log_queue: Widget or root destroyed. Stopping.",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
if self._after_id_log_processor:
|
|
||||||
try:
|
|
||||||
self.root_tk_instance.after_cancel(self._after_id_log_processor)
|
|
||||||
except tk.TclError:
|
|
||||||
pass
|
|
||||||
except Exception as e:
|
|
||||||
print(
|
|
||||||
f"Error cancelling after_id in _process_log_queue (widgets gone): {e}",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
self._after_id_log_processor = None
|
|
||||||
self._is_active = False
|
self._is_active = False
|
||||||
|
if self._internal_after_id and self._root_for_widget_update.winfo_exists():
|
||||||
|
try: self._root_for_widget_update.after_cancel(self._internal_after_id)
|
||||||
|
except Exception: pass
|
||||||
|
self._internal_after_id = None
|
||||||
|
# print("DEBUG: TkinterTextHandler._process_widget_update_queue stopping (inactive/widget gone).", flush=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
had_messages = False
|
||||||
try:
|
try:
|
||||||
while self._is_active:
|
while not self._widget_update_queue.empty():
|
||||||
try:
|
had_messages = True
|
||||||
level_name, msg = self.log_queue.get(block=False, timeout=0.01)
|
level_name, msg = self._widget_update_queue.get_nowait()
|
||||||
except QueueEmpty:
|
if self.text_widget.winfo_exists():
|
||||||
break
|
|
||||||
except Exception as e_get:
|
|
||||||
print(
|
|
||||||
f"Error getting from log queue: {e_get}. Stopping processing for this cycle.",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
break
|
|
||||||
|
|
||||||
if not self._is_active or not (
|
|
||||||
self.text_widget.winfo_exists()
|
|
||||||
and self.root_tk_instance.winfo_exists()
|
|
||||||
):
|
|
||||||
print(
|
|
||||||
"Debug: TkinterTextHandler._process_log_queue: Widgets gone during queue processing loop. Stopping.",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
self._is_active = False
|
|
||||||
break
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.text_widget.configure(state=tk.NORMAL)
|
self.text_widget.configure(state=tk.NORMAL)
|
||||||
if self.text_widget.winfo_exists():
|
self.text_widget.insert(tk.END, msg + "\n", (level_name,))
|
||||||
self.text_widget.insert(tk.END, msg + "\n", (level_name,))
|
self.text_widget.configure(state=tk.DISABLED)
|
||||||
self.text_widget.see(tk.END)
|
else: # Widget destroyed during processing
|
||||||
if self.text_widget.winfo_exists():
|
self._is_active = False; break
|
||||||
self.text_widget.configure(state=tk.DISABLED)
|
self._widget_update_queue.task_done()
|
||||||
self.log_queue.task_done()
|
if had_messages and self.text_widget.winfo_exists():
|
||||||
|
self.text_widget.see(tk.END)
|
||||||
|
|
||||||
except tk.TclError as e_tcl_inner:
|
except tk.TclError as e_tcl:
|
||||||
print(
|
print(f"TkinterTextHandler TclError during widget update: {e_tcl}. Handler stopping.", flush=True)
|
||||||
f"TkinterTextHandler TclError during widget update: {e_tcl_inner}. Attempting to stop handler.",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
self._is_active = False
|
|
||||||
break
|
|
||||||
except Exception as e_inner:
|
|
||||||
print(
|
|
||||||
f"Unexpected error updating text widget: {e_inner}. Attempting to stop handler.",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
self._is_active = False
|
|
||||||
break
|
|
||||||
|
|
||||||
except tk.TclError as e_tcl_outer:
|
|
||||||
print(
|
|
||||||
f"TkinterTextHandler TclError in _process_log_queue (outer): {e_tcl_outer}",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
if self._after_id_log_processor:
|
|
||||||
try:
|
|
||||||
self.root_tk_instance.after_cancel(self._after_id_log_processor)
|
|
||||||
except tk.TclError:
|
|
||||||
pass
|
|
||||||
except Exception as e_cancel:
|
|
||||||
print(
|
|
||||||
f"Error cancelling after_id in _process_log_queue (TclError outer): {e_cancel}",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
self._after_id_log_processor = None
|
|
||||||
self._is_active = False
|
self._is_active = False
|
||||||
return
|
except Exception as e_update:
|
||||||
|
print(f"Unexpected error updating TkinterTextHandler widget: {e_update}. Handler stopping.", flush=True)
|
||||||
except Exception as e_outer:
|
|
||||||
print(
|
|
||||||
f"Unexpected error in TkinterTextHandler._process_log_queue (outer): {e_outer}",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
self._is_active = False
|
self._is_active = False
|
||||||
return
|
|
||||||
|
|
||||||
if (
|
if self._is_active and self._root_for_widget_update.winfo_exists():
|
||||||
self._is_active
|
self._internal_after_id = self._root_for_widget_update.after(
|
||||||
and hasattr(self.root_tk_instance, "winfo_exists")
|
self._internal_poll_interval_ms, self._process_widget_update_queue
|
||||||
and self.root_tk_instance.winfo_exists()
|
)
|
||||||
):
|
else: # Ensure it's cleaned up if we are stopping
|
||||||
try:
|
if self._internal_after_id and self._root_for_widget_update.winfo_exists():
|
||||||
self._after_id_log_processor = self.root_tk_instance.after(
|
try: self._root_for_widget_update.after_cancel(self._internal_after_id)
|
||||||
self.queue_poll_interval_ms, self._process_log_queue
|
except Exception: pass
|
||||||
)
|
self._internal_after_id = None
|
||||||
except tk.TclError:
|
|
||||||
print(
|
|
||||||
"Debug: TkinterTextHandler._process_log_queue: Root destroyed. Cannot reschedule.",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
self._after_id_log_processor = None
|
|
||||||
self._is_active = False
|
|
||||||
except Exception as e_reschedule:
|
|
||||||
print(
|
|
||||||
f"Error rescheduling _process_log_queue: {e_reschedule}", flush=True
|
|
||||||
)
|
|
||||||
self._after_id_log_processor = None
|
|
||||||
self._is_active = False
|
|
||||||
elif self._after_id_log_processor:
|
|
||||||
if (
|
|
||||||
hasattr(self.root_tk_instance, "winfo_exists")
|
|
||||||
and self.root_tk_instance.winfo_exists()
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
self.root_tk_instance.after_cancel(self._after_id_log_processor)
|
|
||||||
except tk.TclError:
|
|
||||||
pass
|
|
||||||
except Exception as e_cancel_final:
|
|
||||||
print(
|
|
||||||
f"Error cancelling after_id in final _process_log_queue check: {e_cancel_final}",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
self._after_id_log_processor = None
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""
|
print("INFO: TkinterTextHandler close called.", flush=True)
|
||||||
Cleans up resources, like stopping the log queue processor.
|
|
||||||
Called when the handler is removed or logging system shuts down.
|
|
||||||
"""
|
|
||||||
self._is_active = False
|
self._is_active = False
|
||||||
|
if self._internal_after_id and hasattr(self, '_root_for_widget_update') and \
|
||||||
if self._after_id_log_processor:
|
self._root_for_widget_update and self._root_for_widget_update.winfo_exists():
|
||||||
if (
|
|
||||||
hasattr(self.root_tk_instance, "winfo_exists")
|
|
||||||
and self.root_tk_instance.winfo_exists()
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
self.root_tk_instance.after_cancel(self._after_id_log_processor)
|
|
||||||
except tk.TclError:
|
|
||||||
print(
|
|
||||||
f"Debug: TclError during after_cancel in TkinterTextHandler.close (root might be gone or invalid).",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
pass
|
|
||||||
except Exception as e_cancel_close:
|
|
||||||
print(
|
|
||||||
f"Error cancelling after_id in close: {e_cancel_close}",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
pass
|
|
||||||
self._after_id_log_processor = None
|
|
||||||
|
|
||||||
while not self.log_queue.empty():
|
|
||||||
try:
|
try:
|
||||||
self.log_queue.get_nowait()
|
self._root_for_widget_update.after_cancel(self._internal_after_id)
|
||||||
pass
|
except Exception: pass
|
||||||
except QueueEmpty:
|
self._internal_after_id = None
|
||||||
break
|
# Drain its internal queue
|
||||||
except Exception as e_q_drain:
|
while not self._widget_update_queue.empty():
|
||||||
print(f"Error draining log queue during close: {e_q_drain}", flush=True)
|
try: self._widget_update_queue.get_nowait(); self._widget_update_queue.task_done()
|
||||||
break
|
except Exception: break
|
||||||
|
|
||||||
super().close()
|
super().close()
|
||||||
|
|
||||||
|
|
||||||
_tkinter_handler_instance: Optional[TkinterTextHandler] = None
|
class QueuePuttingHandler(logging.Handler):
|
||||||
|
"""
|
||||||
|
A simple handler that puts any received LogRecord into a global queue.
|
||||||
|
"""
|
||||||
|
def __init__(self, handler_queue: Queue[logging.LogRecord]):
|
||||||
|
super().__init__()
|
||||||
|
self.handler_queue = handler_queue
|
||||||
|
|
||||||
|
def emit(self, record: logging.LogRecord):
|
||||||
|
try:
|
||||||
|
# We can choose to make a copy of the record if there's any concern
|
||||||
|
# about it being modified before processing, though typically not an issue.
|
||||||
|
# For now, put the original record.
|
||||||
|
self.handler_queue.put_nowait(record)
|
||||||
|
except queue.Full:
|
||||||
|
print(f"CRITICAL: Global log queue is full! Log record for '{record.name}' might be lost.", flush=True)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"CRITICAL: Error putting log record into global queue: {e}", flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _process_global_log_queue():
|
||||||
|
"""
|
||||||
|
GUI Thread: Periodically processes LogRecords from the _global_log_queue
|
||||||
|
and dispatches them to the actual configured handlers.
|
||||||
|
"""
|
||||||
|
global _logging_system_active, _log_processor_after_id
|
||||||
|
# print(f"DEBUG: _process_global_log_queue called. Active: {_logging_system_active}", flush=True) # Can be very verbose
|
||||||
|
|
||||||
|
if not _logging_system_active:
|
||||||
|
if _log_processor_after_id and _tk_root_instance_for_processing and _tk_root_instance_for_processing.winfo_exists():
|
||||||
|
try: _tk_root_instance_for_processing.after_cancel(_log_processor_after_id)
|
||||||
|
except Exception: pass
|
||||||
|
_log_processor_after_id = None
|
||||||
|
return
|
||||||
|
|
||||||
|
if not (_tk_root_instance_for_processing and _tk_root_instance_for_processing.winfo_exists()):
|
||||||
|
# print("DEBUG: Global log queue processor: Tk root instance gone. Stopping.", flush=True)
|
||||||
|
_logging_system_active = False # Stop if root is gone
|
||||||
|
_log_processor_after_id = None
|
||||||
|
return
|
||||||
|
|
||||||
|
processed_count = 0
|
||||||
|
try:
|
||||||
|
while _global_log_queue and not _global_log_queue.empty():
|
||||||
|
if not _logging_system_active: break # Check before processing each item
|
||||||
|
try:
|
||||||
|
record = _global_log_queue.get_nowait()
|
||||||
|
processed_count +=1
|
||||||
|
|
||||||
|
# Dispatch to actual handlers
|
||||||
|
if _actual_console_handler:
|
||||||
|
try: _actual_console_handler.handle(record)
|
||||||
|
except Exception as e_con: print(f"Error in console_handler.handle: {e_con}", flush=True)
|
||||||
|
|
||||||
|
if _actual_file_handler:
|
||||||
|
try: _actual_file_handler.handle(record)
|
||||||
|
except Exception as e_file: print(f"Error in file_handler.handle: {e_file}", flush=True)
|
||||||
|
|
||||||
|
if _actual_tkinter_handler:
|
||||||
|
try: _actual_tkinter_handler.handle(record)
|
||||||
|
except Exception as e_tk: print(f"Error in tkinter_handler.handle: {e_tk}", flush=True)
|
||||||
|
|
||||||
|
_global_log_queue.task_done()
|
||||||
|
except QueueEmpty:
|
||||||
|
break # Should not happen due to outer loop condition, but defensive
|
||||||
|
except Exception as e_proc_item:
|
||||||
|
print(f"Error processing a log item from global queue: {e_proc_item}", flush=True)
|
||||||
|
# Potentially re-queue or log to a failsafe if critical
|
||||||
|
except Exception as e_outer_loop:
|
||||||
|
print(f"Critical error in _process_global_log_queue outer loop: {e_outer_loop}", flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
if _logging_system_active and _tk_root_instance_for_processing.winfo_exists():
|
||||||
|
_log_processor_after_id = _tk_root_instance_for_processing.after(
|
||||||
|
GLOBAL_LOG_QUEUE_POLL_INTERVAL_MS, _process_global_log_queue
|
||||||
|
)
|
||||||
|
# else: print(f"DEBUG: Not rescheduling _process_global_log_queue. Active: {_logging_system_active}", flush=True)
|
||||||
|
|
||||||
|
|
||||||
def setup_logging(
|
def setup_logging(
|
||||||
@ -297,195 +237,157 @@ def setup_logging(
|
|||||||
root_tk_instance: Optional[tk.Tk] = None,
|
root_tk_instance: Optional[tk.Tk] = None,
|
||||||
logging_config_dict: Optional[Dict[str, Any]] = None,
|
logging_config_dict: Optional[Dict[str, Any]] = None,
|
||||||
):
|
):
|
||||||
"""
|
global _global_log_queue, _actual_console_handler, _actual_file_handler
|
||||||
Sets up application-wide logging based on the provided configuration dictionary.
|
global _actual_tkinter_handler, _logging_system_active, _tk_root_instance_for_processing
|
||||||
If logging_config_dict is None, uses basic default settings (console only).
|
global _log_processor_after_id, _base_formatter
|
||||||
"""
|
|
||||||
global _tkinter_handler_instance
|
print("INFO: Configuring centralized queued logging system...", flush=True)
|
||||||
|
|
||||||
|
if _logging_system_active:
|
||||||
|
print("INFO: Logging system already active. Shutting down existing to reconfigure.", flush=True)
|
||||||
|
shutdown_logging_system() # Ensure clean state before re-setup
|
||||||
|
|
||||||
if logging_config_dict is None:
|
if logging_config_dict is None:
|
||||||
print(
|
print("Warning: No logging_config_dict. Using basic console-only defaults for queued logging.", flush=True)
|
||||||
"Warning: No logging_config_dict provided to setup_logging. Using basic default console logging.",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
logging_config_dict = {
|
logging_config_dict = {
|
||||||
"default_root_level": logging.INFO,
|
"default_root_level": logging.INFO, "specific_levels": {},
|
||||||
"specific_levels": {},
|
"format": "%(asctime)s [%(levelname)-8s] %(name)-25s : %(message)s",
|
||||||
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
||||||
"date_format": "%Y-%m-%d %H:%M:%S",
|
"date_format": "%Y-%m-%d %H:%M:%S",
|
||||||
"colors": {logging.INFO: "black"},
|
"colors": {logging.INFO: "black", logging.DEBUG: "blue", logging.WARNING:"orange", logging.ERROR:"red", logging.CRITICAL:"purple"},
|
||||||
"queue_poll_interval_ms": 100,
|
"queue_poll_interval_ms": 100, # For TkinterTextHandler's internal queue
|
||||||
"enable_console": True,
|
"enable_console": True, "enable_file": False,
|
||||||
"enable_file": False,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# --- Extract Config ---
|
||||||
root_log_level = logging_config_dict.get("default_root_level", logging.INFO)
|
root_log_level = logging_config_dict.get("default_root_level", logging.INFO)
|
||||||
specific_levels = logging_config_dict.get("specific_levels", {})
|
specific_levels = logging_config_dict.get("specific_levels", {})
|
||||||
log_format_str = logging_config_dict.get(
|
log_format_str = logging_config_dict.get("format", "%(asctime)s [%(levelname)-8s] %(name)-25s : %(message)s")
|
||||||
"format", "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
||||||
)
|
|
||||||
log_date_format_str = logging_config_dict.get("date_format", "%Y-%m-%d %H:%M:%S")
|
log_date_format_str = logging_config_dict.get("date_format", "%Y-%m-%d %H:%M:%S")
|
||||||
level_colors = logging_config_dict.get("colors", {})
|
level_colors = logging_config_dict.get("colors", {})
|
||||||
queue_poll_interval_ms = logging_config_dict.get("queue_poll_interval_ms", 100)
|
tkinter_handler_poll_ms = logging_config_dict.get("queue_poll_interval_ms", 100) # For TkinterTextHandler's internal queue
|
||||||
enable_console = logging_config_dict.get("enable_console", True)
|
enable_console = logging_config_dict.get("enable_console", True)
|
||||||
|
|
||||||
# MODIFIED: De-commented the line to extract 'enable_file' from the config dictionary.
|
|
||||||
# WHY: The variable 'enable_file' was used later in an 'if' statement but was not defined because this line was commented out.
|
|
||||||
# HOW: Removed the '#' at the beginning of the line.
|
|
||||||
enable_file = logging_config_dict.get("enable_file", False)
|
enable_file = logging_config_dict.get("enable_file", False)
|
||||||
|
|
||||||
formatter = logging.Formatter(log_format_str, datefmt=log_date_format_str)
|
_base_formatter = logging.Formatter(log_format_str, datefmt=log_date_format_str)
|
||||||
|
_global_log_queue = Queue() # Initialize the global queue for LogRecords
|
||||||
|
_tk_root_instance_for_processing = root_tk_instance # Store for the after() loop
|
||||||
|
|
||||||
|
# --- Configure Root Logger ---
|
||||||
root_logger = logging.getLogger()
|
root_logger = logging.getLogger()
|
||||||
|
# Remove ALL existing handlers from the root logger to ensure clean state
|
||||||
handlers_to_remove = []
|
|
||||||
for handler in root_logger.handlers[:]:
|
for handler in root_logger.handlers[:]:
|
||||||
if isinstance(handler, (logging.StreamHandler, logging.FileHandler)):
|
|
||||||
handlers_to_remove.append(handler)
|
|
||||||
elif handler is _tkinter_handler_instance:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
module_logger_internal = logging.getLogger(__name__)
|
|
||||||
module_logger_internal.warning(
|
|
||||||
f"Removing unknown logger handler during setup: {handler}"
|
|
||||||
)
|
|
||||||
handlers_to_remove.append(handler)
|
|
||||||
|
|
||||||
for handler in handlers_to_remove:
|
|
||||||
try:
|
try:
|
||||||
handler.close()
|
handler.close() # Close handler first
|
||||||
root_logger.removeHandler(handler)
|
root_logger.removeHandler(handler)
|
||||||
module_logger_internal = logging.getLogger(__name__)
|
print(f"DEBUG: Removed and closed old handler from root: {handler}", flush=True)
|
||||||
module_logger_internal.debug(f"Removed and closed old handler: {handler}")
|
except Exception as e_rem:
|
||||||
except Exception as e:
|
print(f"Error removing/closing old handler {handler}: {e_rem}", flush=True)
|
||||||
module_logger_internal = logging.getLogger(__name__)
|
root_logger.handlers = [] # Ensure it's empty
|
||||||
module_logger_internal.error(
|
|
||||||
f"Error removing/closing old handler {handler}: {e}", exc_info=False
|
|
||||||
)
|
|
||||||
|
|
||||||
if _tkinter_handler_instance:
|
|
||||||
if _tkinter_handler_instance in root_logger.handlers:
|
|
||||||
try:
|
|
||||||
root_logger.removeHandler(_tkinter_handler_instance)
|
|
||||||
module_logger_internal = logging.getLogger(__name__)
|
|
||||||
module_logger_internal.debug("Removed old TkinterTextHandler instance.")
|
|
||||||
except Exception as e:
|
|
||||||
module_logger_internal = logging.getLogger(__name__)
|
|
||||||
module_logger_internal.error(
|
|
||||||
f"Error removing old TkinterTextHandler instance: {e}",
|
|
||||||
exc_info=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
_tkinter_handler_instance.close()
|
|
||||||
module_logger_internal = logging.getLogger(__name__)
|
|
||||||
module_logger_internal.debug("Closed old TkinterTextHandler instance.")
|
|
||||||
except Exception as e:
|
|
||||||
module_logger_internal = logging.getLogger(__name__)
|
|
||||||
module_logger_internal.error(
|
|
||||||
f"Error closing old TkinterTextHandler instance: {e}", exc_info=False
|
|
||||||
)
|
|
||||||
finally:
|
|
||||||
_tkinter_handler_instance = None
|
|
||||||
|
|
||||||
root_logger.setLevel(root_log_level)
|
root_logger.setLevel(root_log_level)
|
||||||
module_logger_internal = logging.getLogger(__name__)
|
print(f"INFO: Root logger level set to {logging.getLevelName(root_logger.level)}.", flush=True)
|
||||||
module_logger_internal.debug(
|
|
||||||
f"Root logger level set to {logging.getLevelName(root_logger.level)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
# --- Configure Specific Logger Levels ---
|
||||||
for logger_name, level in specific_levels.items():
|
for logger_name, level in specific_levels.items():
|
||||||
named_logger = logging.getLogger(logger_name)
|
named_logger = logging.getLogger(logger_name)
|
||||||
named_logger.setLevel(level)
|
named_logger.setLevel(level)
|
||||||
module_logger_internal.debug(
|
# Ensure propagation is on if they are not root, or if they should also go to root's QueuePuttingHandler
|
||||||
f"Logger '{logger_name}' level set to {logging.getLevelName(named_logger.level)}"
|
named_logger.propagate = True
|
||||||
)
|
print(f"INFO: Logger '{logger_name}' level set to {logging.getLevelName(named_logger.level)}.", flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Create Actual Handlers (but don't add to root logger yet) ---
|
||||||
if enable_console:
|
if enable_console:
|
||||||
try:
|
try:
|
||||||
console_handler = logging.StreamHandler()
|
_actual_console_handler = logging.StreamHandler() # Defaults to stderr
|
||||||
console_handler.setFormatter(formatter)
|
_actual_console_handler.setFormatter(_base_formatter)
|
||||||
root_logger.addHandler(console_handler)
|
_actual_console_handler.setLevel(logging.DEBUG) # Let console handler decide based on record level
|
||||||
module_logger_internal.debug("Console logging enabled and handler added.")
|
print("INFO: Actual ConsoleHandler created.", flush=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
module_logger_internal.error(
|
print(f"ERROR: Failed to create actual ConsoleHandler: {e}", flush=True)
|
||||||
f"Error adding console handler: {e}", exc_info=False
|
_actual_console_handler = None
|
||||||
)
|
|
||||||
|
|
||||||
# MODIFIED: Added the block to configure the file handler if enable_file is True.
|
|
||||||
# WHY: This logic was intended to be here.
|
|
||||||
# HOW: Moved the block from the original code and ensured it's under the 'if enable_file:' condition.
|
|
||||||
if enable_file:
|
if enable_file:
|
||||||
try:
|
try:
|
||||||
from logging.handlers import RotatingFileHandler
|
file_path = logging_config_dict.get("file_path", "flight_monitor_app.log")
|
||||||
|
file_max_bytes = logging_config_dict.get("file_max_bytes", 5 * 1024 * 1024) # 5MB
|
||||||
file_path = logging_config_dict.get("file_path", "app.log")
|
file_backup_count = logging_config_dict.get("file_backup_count", 3)
|
||||||
file_max_bytes = logging_config_dict.get("file_max_bytes", 10 * 1024 * 1024)
|
_actual_file_handler = logging.handlers.RotatingFileHandler(
|
||||||
file_backup_count = logging_config_dict.get("file_backup_count", 5)
|
file_path, maxBytes=file_max_bytes, backupCount=file_backup_count, encoding='utf-8'
|
||||||
file_handler = RotatingFileHandler(
|
|
||||||
file_path, maxBytes=file_max_bytes, backupCount=file_backup_count
|
|
||||||
)
|
|
||||||
file_handler.setFormatter(formatter)
|
|
||||||
root_logger.addHandler(file_handler)
|
|
||||||
module_logger_internal.debug(
|
|
||||||
f"File logging enabled to '{file_path}' and handler added."
|
|
||||||
)
|
)
|
||||||
|
_actual_file_handler.setFormatter(_base_formatter)
|
||||||
|
_actual_file_handler.setLevel(logging.DEBUG) # Let file handler decide
|
||||||
|
print(f"INFO: Actual RotatingFileHandler created for '{file_path}'.", flush=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
module_logger_internal.error(
|
print(f"ERROR: Failed to create actual FileHandler for '{file_path}': {e}", flush=True)
|
||||||
f"Error adding file handler to '{file_path}': {e}", exc_info=True
|
_actual_file_handler = None
|
||||||
)
|
|
||||||
|
|
||||||
if gui_log_widget and root_tk_instance:
|
if gui_log_widget and root_tk_instance:
|
||||||
is_widget_valid = (
|
is_widget_valid = (
|
||||||
isinstance(gui_log_widget, (tk.Text, ScrolledText))
|
isinstance(gui_log_widget, (tk.Text, ScrolledText)) and
|
||||||
and hasattr(gui_log_widget, "winfo_exists")
|
hasattr(gui_log_widget, "winfo_exists") and gui_log_widget.winfo_exists()
|
||||||
and gui_log_widget.winfo_exists()
|
|
||||||
)
|
)
|
||||||
is_root_valid = (
|
if is_widget_valid:
|
||||||
hasattr(root_tk_instance, "winfo_exists")
|
|
||||||
and root_tk_instance.winfo_exists()
|
|
||||||
)
|
|
||||||
|
|
||||||
if not is_widget_valid:
|
|
||||||
print(
|
|
||||||
f"ERROR: GUI log widget is not a valid tk.Text/ScrolledText or does not exist: {type(gui_log_widget)}",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
elif not is_root_valid:
|
|
||||||
print(
|
|
||||||
"WARNING: Root Tk instance provided to setup_logging does not exist.",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
try:
|
try:
|
||||||
_tkinter_handler_instance = TkinterTextHandler(
|
_actual_tkinter_handler = TkinterTextHandler(
|
||||||
text_widget=gui_log_widget,
|
text_widget=gui_log_widget,
|
||||||
root_tk_instance=root_tk_instance,
|
|
||||||
level_colors=level_colors,
|
level_colors=level_colors,
|
||||||
queue_poll_interval_ms=queue_poll_interval_ms,
|
root_tk_instance_for_widget_update=root_tk_instance, # Pass root for its own after loop
|
||||||
)
|
internal_poll_interval_ms=tkinter_handler_poll_ms
|
||||||
_tkinter_handler_instance.setFormatter(formatter)
|
|
||||||
root_logger.addHandler(_tkinter_handler_instance)
|
|
||||||
module_logger_internal = logging.getLogger(__name__)
|
|
||||||
module_logger_internal.info(
|
|
||||||
"GUI logging handler (thread-safe) initialized and attached."
|
|
||||||
)
|
)
|
||||||
|
_actual_tkinter_handler.setFormatter(_base_formatter)
|
||||||
|
_actual_tkinter_handler.setLevel(logging.DEBUG) # Let Tkinter handler decide
|
||||||
|
print("INFO: Actual TkinterTextHandler created.", flush=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_tkinter_handler_instance = None
|
print(f"ERROR: Failed to create actual TkinterTextHandler: {e}", flush=True)
|
||||||
print(
|
_actual_tkinter_handler = None
|
||||||
f"ERROR: Failed to initialize TkinterTextHandler: {e}", flush=True
|
else:
|
||||||
)
|
print("ERROR: GUI log widget invalid or non-existent, cannot create TkinterTextHandler.", flush=True)
|
||||||
|
|
||||||
elif gui_log_widget and not root_tk_instance:
|
elif gui_log_widget and not root_tk_instance:
|
||||||
print(
|
print("WARNING: GUI log widget provided, but root_tk_instance missing for TkinterTextHandler.", flush=True)
|
||||||
"WARNING: GUI log widget provided, but root Tk instance is missing. Cannot initialize GUI logger.",
|
|
||||||
flush=True,
|
|
||||||
)
|
# --- Add ONLY the QueuePuttingHandler to the root logger ---
|
||||||
elif not gui_log_widget and root_tk_instance:
|
if _global_log_queue is not None:
|
||||||
print(
|
queue_putter = QueuePuttingHandler(handler_queue=_global_log_queue)
|
||||||
"DEBUG: Root Tk instance provided, but no GUI log widget. GUI logger not initialized.",
|
queue_putter.setLevel(logging.DEBUG) # Capture all messages from root level downwards
|
||||||
flush=True,
|
root_logger.addHandler(queue_putter)
|
||||||
|
print(f"INFO: QueuePuttingHandler added to root logger. All logs will go to global queue.", flush=True)
|
||||||
|
else:
|
||||||
|
print("CRITICAL ERROR: Global log queue not initialized. Logging will not function correctly.", flush=True)
|
||||||
|
# Fallback to basic console if queue setup failed badly
|
||||||
|
if not _actual_console_handler: # If even this failed
|
||||||
|
_bf = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
||||||
|
_ch = logging.StreamHandler()
|
||||||
|
_ch.setFormatter(_bf)
|
||||||
|
root_logger.addHandler(_ch)
|
||||||
|
print("CRITICAL: Added basic emergency console logger.", flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Start the global log queue processor (if root_tk_instance is available) ---
|
||||||
|
_logging_system_active = True
|
||||||
|
if _tk_root_instance_for_processing and _tk_root_instance_for_processing.winfo_exists():
|
||||||
|
if _log_processor_after_id: # Cancel previous if any (shouldn't happen if shutdown was called)
|
||||||
|
try: _tk_root_instance_for_processing.after_cancel(_log_processor_after_id)
|
||||||
|
except Exception: pass
|
||||||
|
_log_processor_after_id = _tk_root_instance_for_processing.after(
|
||||||
|
GLOBAL_LOG_QUEUE_POLL_INTERVAL_MS, _process_global_log_queue
|
||||||
)
|
)
|
||||||
|
print(f"INFO: Global log queue processor scheduled with Tkinter.after (ID: {_log_processor_after_id}).", flush=True)
|
||||||
|
elif root_tk_instance is None and (gui_log_widget is not None or enable_file or enable_console):
|
||||||
|
# This case means we want logging but have no Tkinter root to schedule the queue processor.
|
||||||
|
# This is problematic for GUI logging. For console/file, logs will just queue up.
|
||||||
|
# A dedicated thread could process the queue if no Tkinter available, but adds complexity.
|
||||||
|
print("WARNING: No Tkinter root instance provided to setup_logging. "
|
||||||
|
"Logs will be queued but not processed by GUI/File/Console handlers unless _process_global_log_queue is run manually or by another mechanism.", flush=True)
|
||||||
|
elif not (enable_console or enable_file or gui_log_widget):
|
||||||
|
print("INFO: No log output handlers (console, file, gui) are enabled. Logs will be queued but not displayed.", flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
print("INFO: Centralized queued logging system setup complete.", flush=True)
|
||||||
|
# Test log
|
||||||
|
test_logger = logging.getLogger("FlightMonitor.LoggerTest")
|
||||||
|
test_logger.info("Logging system initialized. This is a test message from setup_logging.")
|
||||||
|
|
||||||
|
|
||||||
def get_logger(name: str) -> logging.Logger:
|
def get_logger(name: str) -> logging.Logger:
|
||||||
@ -496,30 +398,84 @@ def get_logger(name: str) -> logging.Logger:
|
|||||||
return logging.getLogger(name)
|
return logging.getLogger(name)
|
||||||
|
|
||||||
|
|
||||||
def shutdown_gui_logging():
|
def shutdown_logging_system():
|
||||||
"""
|
"""
|
||||||
Closes and removes the TkinterTextHandler instance from the root logger.
|
Shuts down the centralized logging system, processing remaining logs
|
||||||
This should be called before the Tkinter root window is destroyed.
|
and closing actual handlers.
|
||||||
"""
|
"""
|
||||||
global _tkinter_handler_instance
|
global _logging_system_active, _log_processor_after_id
|
||||||
root_logger = logging.getLogger()
|
global _actual_console_handler, _actual_file_handler, _actual_tkinter_handler
|
||||||
if _tkinter_handler_instance:
|
global _global_log_queue, _tk_root_instance_for_processing
|
||||||
if _tkinter_handler_instance in root_logger.handlers:
|
|
||||||
print(
|
|
||||||
f"INFO: Closing and removing GUI logging handler ({_tkinter_handler_instance.name}).",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
root_logger.removeHandler(_tkinter_handler_instance)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error removing GUI handler from logger: {e}", flush=True)
|
|
||||||
|
|
||||||
|
print("INFO: Initiating shutdown of centralized logging system...", flush=True)
|
||||||
|
_logging_system_active = False # Signal processor to stop
|
||||||
|
|
||||||
|
if _log_processor_after_id and _tk_root_instance_for_processing and _tk_root_instance_for_processing.winfo_exists():
|
||||||
try:
|
try:
|
||||||
_tkinter_handler_instance.close()
|
_tk_root_instance_for_processing.after_cancel(_log_processor_after_id)
|
||||||
print("INFO: GUI logging handler has been shut down.", flush=True)
|
print("INFO: Cancelled global log queue processor task.", flush=True)
|
||||||
except Exception as e:
|
except Exception as e_cancel:
|
||||||
print(f"Error closing GUI logging handler: {e}", flush=True)
|
print(f"Warning: Error cancelling log processor task: {e_cancel}", flush=True)
|
||||||
finally:
|
_log_processor_after_id = None
|
||||||
_tkinter_handler_instance = None
|
|
||||||
else:
|
# Attempt to process any remaining logs in the global queue
|
||||||
print("DEBUG: No active GUI logging handler to shut down.", flush=True)
|
print("INFO: Processing any remaining logs in the global queue before full shutdown...", flush=True)
|
||||||
|
if _global_log_queue:
|
||||||
|
final_processed_count = 0
|
||||||
|
while not _global_log_queue.empty():
|
||||||
|
try:
|
||||||
|
record = _global_log_queue.get_nowait()
|
||||||
|
final_processed_count += 1
|
||||||
|
if _actual_console_handler: _actual_console_handler.handle(record)
|
||||||
|
if _actual_file_handler: _actual_file_handler.handle(record)
|
||||||
|
if _actual_tkinter_handler: _actual_tkinter_handler.handle(record)
|
||||||
|
_global_log_queue.task_done()
|
||||||
|
except QueueEmpty:
|
||||||
|
break
|
||||||
|
except Exception as e_final_proc:
|
||||||
|
print(f"Error during final log processing: {e_final_proc}", flush=True)
|
||||||
|
break # Stop if error during final processing
|
||||||
|
if final_processed_count > 0:
|
||||||
|
print(f"INFO: Processed {final_processed_count} remaining log messages.", flush=True)
|
||||||
|
else:
|
||||||
|
print("INFO: No remaining log messages in global queue to process.", flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
# Close actual handlers
|
||||||
|
if _actual_tkinter_handler:
|
||||||
|
try:
|
||||||
|
print("INFO: Closing actual TkinterTextHandler.", flush=True)
|
||||||
|
_actual_tkinter_handler.close()
|
||||||
|
except Exception as e: print(f"Error closing TkinterTextHandler: {e}", flush=True)
|
||||||
|
_actual_tkinter_handler = None
|
||||||
|
|
||||||
|
if _actual_console_handler:
|
||||||
|
try:
|
||||||
|
print("INFO: Closing actual ConsoleHandler.", flush=True)
|
||||||
|
_actual_console_handler.close()
|
||||||
|
except Exception as e: print(f"Error closing ConsoleHandler: {e}", flush=True)
|
||||||
|
_actual_console_handler = None
|
||||||
|
|
||||||
|
if _actual_file_handler:
|
||||||
|
try:
|
||||||
|
print("INFO: Closing actual FileHandler.", flush=True)
|
||||||
|
_actual_file_handler.close()
|
||||||
|
except Exception as e: print(f"Error closing FileHandler: {e}", flush=True)
|
||||||
|
_actual_file_handler = None
|
||||||
|
|
||||||
|
# Clear the root logger's handlers (should only be the QueuePuttingHandler)
|
||||||
|
root_logger = logging.getLogger()
|
||||||
|
for handler in root_logger.handlers[:]:
|
||||||
|
try:
|
||||||
|
if isinstance(handler, QueuePuttingHandler): # Specifically target our handler
|
||||||
|
handler.close()
|
||||||
|
root_logger.removeHandler(handler)
|
||||||
|
print(f"INFO: Removed and closed QueuePuttingHandler: {handler}", flush=True)
|
||||||
|
except Exception as e_rem_qph:
|
||||||
|
print(f"Error removing QueuePuttingHandler: {e_rem_qph}", flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
_global_log_queue = None # Allow it to be garbage collected
|
||||||
|
_tk_root_instance_for_processing = None # Clear reference
|
||||||
|
_base_formatter = None
|
||||||
|
print("INFO: Centralized logging system shutdown complete.", flush=True)
|
||||||
Loading…
Reference in New Issue
Block a user