fix double logging initialization, fix minor
This commit is contained in:
parent
4042146ba9
commit
aed7be91a4
@ -6,10 +6,10 @@ import tkinter as tk
|
||||
# HOW: Added import.
|
||||
from .data.logging_config import LOGGING_CONFIG # Import the config dictionary
|
||||
|
||||
# MODIFIED: Import the logger setup function AND get_logger function
|
||||
# WHY: Need to call the centralized setup function early and get logger instances.
|
||||
# HOW: Added get_logger to the import statement.
|
||||
from .utils.logger import setup_logging, get_logger
|
||||
# MODIFIED: Import the new setup_basic_logging function AND get_logger function
|
||||
# WHY: Need to call the centralized basic setup function early and get logger instances.
|
||||
# HOW: Changed setup_logging to setup_basic_logging.
|
||||
from .utils.logger import setup_basic_logging, get_logger # MODIFIED HERE
|
||||
|
||||
from .gui.main_window import MainWindow
|
||||
from .controller.app_controller import AppController
|
||||
@ -18,41 +18,31 @@ from .controller.app_controller import AppController
|
||||
def main():
|
||||
"""
|
||||
Main function to launch the Flight Monitor application.
|
||||
Initializes the Tkinter root, sets up logging, creates the controller
|
||||
Initializes the Tkinter root, sets up basic logging, creates the controller
|
||||
and the main window, and starts the event loop.
|
||||
"""
|
||||
# Initialize the Tkinter root window FIRST
|
||||
root = tk.Tk()
|
||||
|
||||
# MODIFIED: Setup logging AFTER Tkinter root is created
|
||||
# WHY: The TkinterTextHandler requires a root instance.
|
||||
# Setup logging as early as possible after dependencies are met.
|
||||
# HOW: Called setup_logging here, passing the necessary arguments including the config.
|
||||
setup_logging(
|
||||
gui_log_widget=None, root_tk_instance=root, logging_config_dict=LOGGING_CONFIG
|
||||
# MODIFIED: Setup basic logging AFTER Tkinter root is created
|
||||
# WHY: The Tkinter-based log processor needs a root instance to schedule itself.
|
||||
# The TkinterTextHandler itself will be added later by MainWindow.
|
||||
# HOW: Called setup_basic_logging here, passing the necessary arguments.
|
||||
setup_basic_logging( # MODIFIED HERE
|
||||
root_tk_instance_for_processor=root, # MODIFIED: Pass root for the log processor
|
||||
logging_config_dict=LOGGING_CONFIG
|
||||
)
|
||||
|
||||
# Now that basic logging is set up, we can use module_logger
|
||||
# MODIFIED: Call get_logger directly to get the logger for this module.
|
||||
# WHY: get_logger is a function, not a method of setup_logging.
|
||||
# HOW: Changed setup_logging.get_logger to just get_logger.
|
||||
module_logger_main = get_logger(
|
||||
__name__
|
||||
) # Get logger using the function from logger.py
|
||||
# Now that basic logging is set up (console, file, queue), we can use module_logger
|
||||
module_logger_main = get_logger(__name__)
|
||||
module_logger_main.info("Application starting...")
|
||||
|
||||
# Create the application controller
|
||||
app_controller = AppController()
|
||||
|
||||
# Create the main application window
|
||||
# Pass the root and the controller to the MainWindow.
|
||||
# MainWindow itself will now handle the creation and passing of its log widget
|
||||
# to setup_logging (which we already called above, but will be called again by MW if it needs to).
|
||||
# Ideally, MainWindow's __init__ should just receive the logger configuration somehow
|
||||
# and pass its log widget to setup_logging if it exists.
|
||||
# With the current design, MainWindow will call setup_logging *again* in its __init__,
|
||||
# passing its log widget. This is fine; setup_logging is designed to handle re-initialization
|
||||
# and replace handlers.
|
||||
# MainWindow itself will now handle the creation and specific addition of its log widget
|
||||
# to the logging system using a new dedicated function in the logger module.
|
||||
main_app_window = MainWindow(root, app_controller)
|
||||
|
||||
# Set the main window instance in the controller
|
||||
@ -63,13 +53,10 @@ def main():
|
||||
root.mainloop()
|
||||
module_logger_main.info("Tkinter main loop finished.")
|
||||
|
||||
# Cleanup should happen when the window is closed (WM_DELETE_WINDOW)
|
||||
# or after mainloop exits (if it exits cleanly).
|
||||
# The on_application_exit in the controller handles non-GUI cleanup.
|
||||
# The shutdown_gui_logging is called by MainWindow's _on_closing.
|
||||
|
||||
# Cleanup is handled by MainWindow's _on_closing method, which in turn
|
||||
# calls controller.on_application_exit and logger.shutdown_logging_system.
|
||||
module_logger_main.info("Application finished.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
File diff suppressed because it is too large
Load Diff
@ -3,7 +3,7 @@ import logging
|
||||
import logging.handlers # For RotatingFileHandler
|
||||
import tkinter as tk
|
||||
from tkinter.scrolledtext import ScrolledText
|
||||
from queue import Queue, Empty as QueueEmpty # Renamed for clarity
|
||||
from queue import Queue, Empty as QueueEmpty # Renamed for clarity
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
# --- Module-level globals for the new centralized logging queue system ---
|
||||
@ -94,10 +94,10 @@ class TkinterTextHandler(logging.Handler):
|
||||
# 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,
|
||||
)
|
||||
# print(
|
||||
# f"DEBUG: TkinterTextHandler.emit called but inactive or widget gone. Record: {record.getMessage()}",
|
||||
# flush=True,
|
||||
# )
|
||||
return
|
||||
try:
|
||||
msg = self.format(
|
||||
@ -169,7 +169,7 @@ class TkinterTextHandler(logging.Handler):
|
||||
self._internal_after_id = None
|
||||
|
||||
def close(self):
|
||||
print("INFO: TkinterTextHandler close called.", flush=True)
|
||||
# print("INFO: TkinterTextHandler close called.", flush=True) # Reduced verbosity
|
||||
self._is_active = False
|
||||
if (
|
||||
self._internal_after_id
|
||||
@ -203,11 +203,8 @@ class QueuePuttingHandler(logging.Handler):
|
||||
|
||||
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:
|
||||
except queue.Full: # Standard library 'queue.Full'
|
||||
print(
|
||||
f"CRITICAL: Global log queue is full! Log record for '{record.name}' might be lost.",
|
||||
flush=True,
|
||||
@ -224,7 +221,7 @@ def _process_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
|
||||
# print(f"DEBUG: _process_global_log_queue called. Active: {_logging_system_active}", flush=True)
|
||||
|
||||
if not _logging_system_active:
|
||||
if (
|
||||
@ -243,8 +240,7 @@ def _process_global_log_queue():
|
||||
_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
|
||||
_logging_system_active = False
|
||||
_log_processor_after_id = None
|
||||
return
|
||||
|
||||
@ -252,12 +248,11 @@ def _process_global_log_queue():
|
||||
try:
|
||||
while _global_log_queue and not _global_log_queue.empty():
|
||||
if not _logging_system_active:
|
||||
break # Check before processing each item
|
||||
break
|
||||
try:
|
||||
record = _global_log_queue.get_nowait()
|
||||
processed_count += 1
|
||||
|
||||
# Dispatch to actual handlers
|
||||
if _actual_console_handler:
|
||||
try:
|
||||
_actual_console_handler.handle(record)
|
||||
@ -278,13 +273,12 @@ def _process_global_log_queue():
|
||||
|
||||
_global_log_queue.task_done()
|
||||
except QueueEmpty:
|
||||
break # Should not happen due to outer loop condition, but defensive
|
||||
break
|
||||
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}",
|
||||
@ -295,107 +289,82 @@ def _process_global_log_queue():
|
||||
_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(
|
||||
gui_log_widget: Optional[tk.Text] = None,
|
||||
root_tk_instance: Optional[tk.Tk] = None,
|
||||
def setup_basic_logging( # MODIFIED: Renamed from setup_logging
|
||||
root_tk_instance_for_processor: Optional[tk.Tk], # MODIFIED: Now mandatory for processor
|
||||
logging_config_dict: Optional[Dict[str, Any]] = None,
|
||||
):
|
||||
global _global_log_queue, _actual_console_handler, _actual_file_handler
|
||||
global _actual_tkinter_handler, _logging_system_active, _tk_root_instance_for_processing
|
||||
global _log_processor_after_id, _base_formatter
|
||||
|
||||
print("INFO: Configuring centralized queued logging system...", flush=True)
|
||||
global _logging_system_active, _tk_root_instance_for_processing
|
||||
global _log_processor_after_id, _base_formatter, _actual_tkinter_handler # _actual_tkinter_handler should be None here
|
||||
|
||||
# This function should only be called ONCE.
|
||||
if _logging_system_active:
|
||||
print(
|
||||
"INFO: Logging system already active. Shutting down existing to reconfigure.",
|
||||
"WARNING: setup_basic_logging called but logging system is already active. Ignoring call.",
|
||||
flush=True,
|
||||
)
|
||||
shutdown_logging_system() # Ensure clean state before re-setup
|
||||
return
|
||||
|
||||
print("INFO: Configuring basic centralized queued logging system...", flush=True)
|
||||
_actual_tkinter_handler = None # Ensure it's None at basic setup
|
||||
|
||||
if logging_config_dict is None:
|
||||
print(
|
||||
"Warning: No logging_config_dict. Using basic console-only defaults for queued logging.",
|
||||
flush=True,
|
||||
)
|
||||
# Simplified default, as Tkinter parts are handled by add_tkinter_handler
|
||||
logging_config_dict = {
|
||||
"default_root_level": logging.INFO,
|
||||
"specific_levels": {},
|
||||
"format": "%(asctime)s [%(levelname)-8s] %(name)-25s : %(message)s",
|
||||
"date_format": "%Y-%m-%d %H:%M:%S",
|
||||
"colors": {
|
||||
logging.INFO: "black",
|
||||
logging.DEBUG: "blue",
|
||||
logging.WARNING: "orange",
|
||||
logging.ERROR: "red",
|
||||
logging.CRITICAL: "purple",
|
||||
},
|
||||
"queue_poll_interval_ms": 100, # For TkinterTextHandler's internal queue
|
||||
"enable_console": True,
|
||||
"enable_file": False,
|
||||
}
|
||||
|
||||
# --- Extract Config ---
|
||||
root_log_level = logging_config_dict.get("default_root_level", logging.INFO)
|
||||
specific_levels = logging_config_dict.get("specific_levels", {})
|
||||
log_format_str = logging_config_dict.get(
|
||||
"format", "%(asctime)s [%(levelname)-8s] %(name)-25s : %(message)s"
|
||||
)
|
||||
log_date_format_str = logging_config_dict.get("date_format", "%Y-%m-%d %H:%M:%S")
|
||||
level_colors = logging_config_dict.get("colors", {})
|
||||
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_file = logging_config_dict.get("enable_file", False)
|
||||
|
||||
_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
|
||||
_global_log_queue = Queue()
|
||||
_tk_root_instance_for_processing = root_tk_instance_for_processor
|
||||
|
||||
# --- Configure Root Logger ---
|
||||
root_logger = logging.getLogger()
|
||||
# Remove ALL existing handlers from the root logger to ensure clean state
|
||||
for handler in root_logger.handlers[:]:
|
||||
for handler in root_logger.handlers[:]: # Clear any pre-existing handlers (e.g. from basicConfig)
|
||||
try:
|
||||
handler.close() # Close handler first
|
||||
handler.close()
|
||||
root_logger.removeHandler(handler)
|
||||
print(
|
||||
f"DEBUG: Removed and closed old handler from root: {handler}",
|
||||
flush=True,
|
||||
)
|
||||
except Exception as e_rem:
|
||||
print(f"Error removing/closing old handler {handler}: {e_rem}", flush=True)
|
||||
root_logger.handlers = [] # Ensure it's empty
|
||||
|
||||
except Exception: pass
|
||||
root_logger.handlers = []
|
||||
root_logger.setLevel(root_log_level)
|
||||
print(
|
||||
f"INFO: Root logger level set to {logging.getLevelName(root_logger.level)}.",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
# --- Configure Specific Logger Levels ---
|
||||
for logger_name, level in specific_levels.items():
|
||||
named_logger = logging.getLogger(logger_name)
|
||||
named_logger.setLevel(level)
|
||||
# Ensure propagation is on if they are not root, or if they should also go to root's QueuePuttingHandler
|
||||
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:
|
||||
try:
|
||||
_actual_console_handler = logging.StreamHandler() # Defaults to stderr
|
||||
_actual_console_handler = logging.StreamHandler()
|
||||
_actual_console_handler.setFormatter(_base_formatter)
|
||||
_actual_console_handler.setLevel(
|
||||
logging.DEBUG
|
||||
) # Let console handler decide based on record level
|
||||
_actual_console_handler.setLevel(logging.DEBUG)
|
||||
print("INFO: Actual ConsoleHandler created.", flush=True)
|
||||
except Exception as e:
|
||||
print(f"ERROR: Failed to create actual ConsoleHandler: {e}", flush=True)
|
||||
@ -404,9 +373,7 @@ def setup_logging(
|
||||
if enable_file:
|
||||
try:
|
||||
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_max_bytes = logging_config_dict.get("file_max_bytes", 5 * 1024 * 1024)
|
||||
file_backup_count = logging_config_dict.get("file_backup_count", 3)
|
||||
_actual_file_handler = logging.handlers.RotatingFileHandler(
|
||||
file_path,
|
||||
@ -415,125 +382,103 @@ def setup_logging(
|
||||
encoding="utf-8",
|
||||
)
|
||||
_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,
|
||||
)
|
||||
_actual_file_handler.setLevel(logging.DEBUG)
|
||||
print(f"INFO: Actual RotatingFileHandler created for '{file_path}'.", flush=True)
|
||||
except Exception as e:
|
||||
print(
|
||||
f"ERROR: Failed to create actual FileHandler for '{file_path}': {e}",
|
||||
flush=True,
|
||||
)
|
||||
print(f"ERROR: Failed to create actual FileHandler for '{file_path}': {e}", flush=True)
|
||||
_actual_file_handler = None
|
||||
|
||||
if gui_log_widget and root_tk_instance:
|
||||
is_widget_valid = (
|
||||
isinstance(gui_log_widget, (tk.Text, ScrolledText))
|
||||
and hasattr(gui_log_widget, "winfo_exists")
|
||||
and gui_log_widget.winfo_exists()
|
||||
)
|
||||
if is_widget_valid:
|
||||
try:
|
||||
_actual_tkinter_handler = TkinterTextHandler(
|
||||
text_widget=gui_log_widget,
|
||||
level_colors=level_colors,
|
||||
root_tk_instance_for_widget_update=root_tk_instance, # Pass root for its own after loop
|
||||
internal_poll_interval_ms=tkinter_handler_poll_ms,
|
||||
)
|
||||
_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:
|
||||
print(
|
||||
f"ERROR: Failed to create actual TkinterTextHandler: {e}",
|
||||
flush=True,
|
||||
)
|
||||
_actual_tkinter_handler = None
|
||||
else:
|
||||
print(
|
||||
"ERROR: GUI log widget invalid or non-existent, cannot create TkinterTextHandler.",
|
||||
flush=True,
|
||||
)
|
||||
elif gui_log_widget and not root_tk_instance:
|
||||
print(
|
||||
"WARNING: GUI log widget provided, but root_tk_instance missing for TkinterTextHandler.",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
# --- Add ONLY the QueuePuttingHandler to the root logger ---
|
||||
if _global_log_queue is not None:
|
||||
queue_putter = QueuePuttingHandler(handler_queue=_global_log_queue)
|
||||
queue_putter.setLevel(
|
||||
logging.DEBUG
|
||||
) # Capture all messages from root level downwards
|
||||
queue_putter.setLevel(logging.DEBUG)
|
||||
root_logger.addHandler(queue_putter)
|
||||
print(
|
||||
f"INFO: QueuePuttingHandler added to root logger. All logs will go to global queue.",
|
||||
flush=True,
|
||||
)
|
||||
print(f"INFO: QueuePuttingHandler added to root logger.", 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)
|
||||
print("CRITICAL ERROR: Global log queue not initialized in basic setup!", 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
|
||||
if _log_processor_after_id: # Should be None here, but defensive
|
||||
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(f"INFO: Global log queue processor scheduled (ID: {_log_processor_after_id}).", flush=True)
|
||||
elif not _tk_root_instance_for_processing:
|
||||
print("WARNING: No Tkinter root instance for processor. Logs will queue but not be dispatched by this setup function alone.", 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."
|
||||
print("INFO: Basic centralized queued logging system setup complete.", flush=True)
|
||||
logging.getLogger("FlightMonitor.LoggerTest").info(
|
||||
"Basic logging initialized. This is a test from setup_basic_logging."
|
||||
)
|
||||
|
||||
|
||||
def add_tkinter_handler( # NEW FUNCTION
|
||||
gui_log_widget: tk.Text,
|
||||
root_tk_instance_for_gui_handler: tk.Tk,
|
||||
logging_config_dict: Dict[str, Any],
|
||||
):
|
||||
global _actual_tkinter_handler, _base_formatter
|
||||
|
||||
if not _logging_system_active:
|
||||
print("ERROR: Cannot add Tkinter handler, basic logging system not active.", flush=True)
|
||||
return
|
||||
if not _base_formatter:
|
||||
print("ERROR: Cannot add Tkinter handler, base formatter not set.", flush=True)
|
||||
return
|
||||
|
||||
print("INFO: Attempting to add TkinterTextHandler...", flush=True)
|
||||
|
||||
if _actual_tkinter_handler: # If one already exists, close and prepare to replace
|
||||
print("INFO: Existing TkinterTextHandler found, closing it before adding new one.", flush=True)
|
||||
try:
|
||||
_actual_tkinter_handler.close()
|
||||
except Exception as e_close_old_tk:
|
||||
print(f"Warning: Error closing old TkinterTextHandler: {e_close_old_tk}", flush=True)
|
||||
_actual_tkinter_handler = None
|
||||
|
||||
is_widget_valid = (
|
||||
isinstance(gui_log_widget, (tk.Text, ScrolledText))
|
||||
and hasattr(gui_log_widget, "winfo_exists")
|
||||
and gui_log_widget.winfo_exists()
|
||||
)
|
||||
is_root_valid = (
|
||||
root_tk_instance_for_gui_handler
|
||||
and hasattr(root_tk_instance_for_gui_handler, "winfo_exists")
|
||||
and root_tk_instance_for_gui_handler.winfo_exists()
|
||||
)
|
||||
|
||||
if is_widget_valid and is_root_valid:
|
||||
try:
|
||||
level_colors = logging_config_dict.get("colors", {})
|
||||
tkinter_handler_poll_ms = logging_config_dict.get("queue_poll_interval_ms", 100)
|
||||
|
||||
_actual_tkinter_handler = TkinterTextHandler(
|
||||
text_widget=gui_log_widget,
|
||||
level_colors=level_colors,
|
||||
root_tk_instance_for_widget_update=root_tk_instance_for_gui_handler,
|
||||
internal_poll_interval_ms=tkinter_handler_poll_ms,
|
||||
)
|
||||
_actual_tkinter_handler.setFormatter(_base_formatter)
|
||||
_actual_tkinter_handler.setLevel(logging.DEBUG)
|
||||
print("INFO: TkinterTextHandler added and configured successfully.", flush=True)
|
||||
logging.getLogger("FlightMonitor.LoggerTest").info(
|
||||
"TkinterTextHandler added. This is a test from add_tkinter_handler."
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"ERROR: Failed to create and add TkinterTextHandler: {e}", flush=True)
|
||||
_actual_tkinter_handler = None
|
||||
else:
|
||||
if not is_widget_valid:
|
||||
print("ERROR: GUI log widget invalid or non-existent, cannot add TkinterTextHandler.", flush=True)
|
||||
if not is_root_valid:
|
||||
print("ERROR: Root Tk instance for GUI handler invalid or non-existent.", flush=True)
|
||||
|
||||
|
||||
def get_logger(name: str) -> logging.Logger:
|
||||
"""
|
||||
Retrieves a logger instance by name.
|
||||
@ -551,8 +496,12 @@ def shutdown_logging_system():
|
||||
global _actual_console_handler, _actual_file_handler, _actual_tkinter_handler
|
||||
global _global_log_queue, _tk_root_instance_for_processing
|
||||
|
||||
if not _logging_system_active and not _global_log_queue: # Extra check if it was never really active
|
||||
print("INFO: Logging system shutdown called, but system was not fully active or already shut down.", flush=True)
|
||||
return
|
||||
|
||||
print("INFO: Initiating shutdown of centralized logging system...", flush=True)
|
||||
_logging_system_active = False # Signal processor to stop
|
||||
_logging_system_active = False
|
||||
|
||||
if (
|
||||
_log_processor_after_id
|
||||
@ -561,89 +510,62 @@ def shutdown_logging_system():
|
||||
):
|
||||
try:
|
||||
_tk_root_instance_for_processing.after_cancel(_log_processor_after_id)
|
||||
print("INFO: Cancelled global log queue processor task.", flush=True)
|
||||
# print("INFO: Cancelled global log queue processor task.", flush=True) # Reduced verbosity
|
||||
except Exception as e_cancel:
|
||||
print(
|
||||
f"Warning: Error cancelling log processor task: {e_cancel}", flush=True
|
||||
)
|
||||
print(f"Warning: Error cancelling log processor task: {e_cancel}", flush=True)
|
||||
_log_processor_after_id = None
|
||||
|
||||
# Attempt to process any remaining logs in the global queue
|
||||
print(
|
||||
"INFO: Processing any remaining logs in the global queue before full shutdown...",
|
||||
flush=True,
|
||||
)
|
||||
# print("INFO: Processing any remaining logs in the global queue before full shutdown...", flush=True) # Reduced
|
||||
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)
|
||||
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 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,
|
||||
)
|
||||
break
|
||||
# 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)
|
||||
# print("INFO: Closing actual TkinterTextHandler.", flush=True) # Reduced
|
||||
_actual_tkinter_handler.close()
|
||||
except Exception as e:
|
||||
print(f"Error closing TkinterTextHandler: {e}", flush=True)
|
||||
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)
|
||||
# print("INFO: Closing actual ConsoleHandler.", flush=True) # Reduced
|
||||
_actual_console_handler.close()
|
||||
except Exception as e:
|
||||
print(f"Error closing ConsoleHandler: {e}", flush=True)
|
||||
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)
|
||||
# print("INFO: Closing actual FileHandler.", flush=True) # Reduced
|
||||
_actual_file_handler.close()
|
||||
except Exception as e:
|
||||
print(f"Error closing FileHandler: {e}", flush=True)
|
||||
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
|
||||
if isinstance(handler, QueuePuttingHandler):
|
||||
handler.close()
|
||||
root_logger.removeHandler(handler)
|
||||
print(
|
||||
f"INFO: Removed and closed QueuePuttingHandler: {handler}",
|
||||
flush=True,
|
||||
)
|
||||
# print(f"INFO: Removed and closed QueuePuttingHandler: {handler}", flush=True) # Reduced
|
||||
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
|
||||
_global_log_queue = None
|
||||
_tk_root_instance_for_processing = None
|
||||
_base_formatter = None
|
||||
print("INFO: Centralized logging system shutdown complete.", flush=True)
|
||||
print("INFO: Centralized logging system shutdown complete.", flush=True)
|
||||
118
todo.md
118
todo.md
@ -324,3 +324,121 @@ Considerando le aree di miglioramento ecco una roadmap di sviluppo. Ho ipotizzat
|
||||
* **Design:** Mantieni un design modulare e flessibile per facilitare l'aggiunta di nuove funzionalità in futuro.
|
||||
|
||||
Dimmi cosa ne pensi di questa proposta e quali sono le tue priorità, così possiamo elaborare un piano di sviluppo più dettagliato.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Ottima analisi preliminare! Hai colto molto bene la struttura generale dell'applicazione e hai già individuato alcuni punti chiave.
|
||||
|
||||
Sono d'accordo con te, la priorità assoluta è risolvere il problema di inizializzazione in `FullFlightDetailsWindow`. Quel `self.tk IS MISSING` è decisamente un campanello d'allarme e probabilmente la causa del fallimento della chiamata a `self.protocol`.
|
||||
|
||||
Ecco un riassunto di ciò che ho compreso dalla tua analisi e alcuni pensieri iniziali prima di passare a un piano di sviluppo più dettagliato:
|
||||
|
||||
**Punti di Forza Rilevati:**
|
||||
|
||||
* **Struttura MVC (o tentativo di):** L'organizzazione in cartelle `gui`, `controller`, `data`, `utils`, `map` è un buon inizio per una separazione delle responsabilità.
|
||||
* **Modello Canonico (`CanonicalFlightState`):** Fondamentale per disaccoppiare la logica di business dalle fonti di dati specifiche.
|
||||
* **Configurazione Centralizzata (`config.py`, `logging_config.py`):** Facilita la gestione delle impostazioni globali.
|
||||
* **Logging Accodato per la GUI (`logger.py`):** Ottima soluzione per evitare che il logging blocchi l'interfaccia utente.
|
||||
* **Gestione Asincrona Dati Live:** L'uso di un thread per `OpenSkyLiveAdapter` e di code per la comunicazione con il controller è corretto per non bloccare la GUI durante il recupero dei dati.
|
||||
* **Gestione Asincrona Rendering Mappa (`MapCanvasManager`):** Anche qui, l'uso di un thread worker per il rendering delle tile e degli overlay è una scelta eccellente per mantenere la GUI reattiva.
|
||||
* **Gestione Cache Tile (`MapTileManager`):** Importante per le performance e per l'uso offline (anche se il download offline non è esplicitamente menzionato come requisito, la cache aiuta).
|
||||
* **Modularità Componenti Mappa:** La separazione tra `MapCanvasManager`, `MapTileManager`, `MapService`, `map_drawing` e `map_utils` è buona.
|
||||
* **Gestione Dipendenze Opzionali:** La presenza di flag come `PIL_IMAGE_LIB_AVAILABLE` indica una buona gestione delle librerie non strettamente essenziali per il funzionamento base.
|
||||
|
||||
**Aree Chiave da Indagare e Potenziali Incongruenze (come hai giustamente notato):**
|
||||
|
||||
1. **`FullFlightDetailsWindow.__init__`:**
|
||||
* **Problema `super().__init__()` e `self.tk`:** Come discusso, questo è critico. `super().__init__(parent)` deve essere chiamato prima di qualsiasi operazione che si aspetti che `self` sia un widget Tkinter completamente formato. Questo include `self.title()`, `self.geometry()`, `self.protocol()`, e la creazione di qualsiasi widget figlio *direttamente* su `self` (anche se i widget figli sono generalmente messi su frame interni, il Toplevel stesso deve essere inizializzato).
|
||||
* **Azione Immediata:** Correggere l'ordine delle chiamate in `__init__`.
|
||||
|
||||
2. **Coerenza e Centralizzazione delle Utility:**
|
||||
* **Disegno Testo su Placeholder:** Funzioni come `_draw_text_on_placeholder` appaiono sia in `MapCanvasManager` che in `MapTileManager`. Potrebbero essere centralizzate in `map_drawing.py` o `map_utils.py`.
|
||||
* **Costanti di Disegno Tracce:** Attualmente, `TRACK_HISTORY_POINTS` e `TRACK_LINE_WIDTH` vengono letti in `map_drawing.py` da `app_config` (tramite `map_constants`). Se queste devono essere configurabili dinamicamente dall'utente (es. tramite la GUI in `MainWindow` che ha `track_length_spinbox`), il valore aggiornato deve essere propagato correttamente a `MapCanvasManager` e quindi passato al thread worker che usa `map_drawing`. `MapCanvasManager.set_max_track_points` sembra fare questo per `max_track_points`, ma `TRACK_LINE_WIDTH` sembra più statico.
|
||||
|
||||
3. **Robustezza e Gestione Errori:**
|
||||
* **Dipendenze Mancanti:** La gestione attuale con flag è buona, ma assicurarsi che l'utente riceva un feedback chiaro se una funzionalità chiave (come la mappa) è disabilitata.
|
||||
* **Thread Safety:** L'uso di `_map_data_lock` in `MapCanvasManager` è corretto. Una rapida scorsa suggerisce che sia usato nei punti giusti, ma una revisione più approfondita è sempre utile quando si lavora con thread e dati condivisi.
|
||||
|
||||
4. **Interfaccia Utente e Esperienza Utente (UX):**
|
||||
* **Feedback Validazione BBox:** Come hai suggerito, un feedback più immediato nella GUI per input BBox non validi sarebbe utile.
|
||||
* **Completamento Funzionalità Placeholder:** Le sezioni "History" e "Live: Airport" sono chiaramente da sviluppare.
|
||||
|
||||
**Piano di Sviluppo Proposto (Alto Livello):**
|
||||
|
||||
Concordo con la tua analisi delle priorità. Ecco una possibile strutturazione del piano di sviluppo, che possiamo poi affinare insieme:
|
||||
|
||||
**FASE 1: Stabilizzazione e Correzioni Critiche (Priorità Massima)**
|
||||
|
||||
1. **Risolvere Inizializzazione `FullFlightDetailsWindow`:**
|
||||
* **Obiettivo:** Assicurare che la finestra dei dettagli completi si apra senza errori e funzioni come previsto.
|
||||
* **Azioni:**
|
||||
* Rivedere `__init__` come da tua analisi: `super().__init__(parent)` deve precedere l'uso di metodi specifici del Toplevel.
|
||||
* Testare approfonditamente l'apertura e la chiusura della finestra.
|
||||
2. **Verifica Gestione Errori Critici:**
|
||||
* **Obiettivo:** Assicurare che l'applicazione gestisca con grazia la mancanza di dipendenze chiave (Pillow, mercantile per la mappa) mostrando messaggi chiari e disabilitando le funzionalità impattate senza crashare.
|
||||
* **Azioni:** Testare l'avvio dell'applicazione in un ambiente virtuale dove mancano queste librerie.
|
||||
3. **Revisione Logging Iniziale:**
|
||||
* **Obiettivo:** Assicurare che il logging sia informativo fin dalle prime fasi di avvio e che non ci siano errori nella sua configurazione.
|
||||
* **Azioni:** Controllare i primi log emessi all'avvio.
|
||||
|
||||
**FASE 2: Miglioramenti Strutturali e Usabilità Base (Breve-Medio Termine)**
|
||||
|
||||
1. **Refactoring Componenti Mappa:**
|
||||
* **Obiettivo:** Migliorare la manutenibilità e ridurre la duplicazione di codice.
|
||||
* **Azioni:**
|
||||
* Centralizzare le utility di disegno comuni (es. testo su placeholder, caricamento font) in `map_drawing.py` o `map_utils.py`.
|
||||
* Rivedere come `TRACK_LINE_WIDTH` viene gestito; idealmente dovrebbe essere una configurazione passata a `MapCanvasManager` e poi al worker, simile a `max_track_points`.
|
||||
2. **Migliorare Feedback Utente GUI:**
|
||||
* **Obiettivo:** Rendere l'interfaccia più intuitiva.
|
||||
* **Azioni:**
|
||||
* Implementare validazione BBox in tempo reale (o al momento del focus-out) nei campi di input di `MainWindow`.
|
||||
* Assicurare che i messaggi di stato e gli indicatori (semaforo) siano sempre chiari e riflettano lo stato corrente dell'applicazione.
|
||||
3. **Consolidare la Logica di `MapCanvasManager`:**
|
||||
* **Obiettivo:** Data la sua complessità, assicurarsi che la gestione dello stato (zoom, centro, BBox target) sia robusta e che le interazioni tra thread GUI e worker siano impeccabili.
|
||||
* **Azioni:** Revisione specifica del flusso di richieste e risultati di rendering, specialmente in scenari di interazioni utente rapide (pan/zoom veloci).
|
||||
|
||||
**FASE 3: Implementazione Funzionalità Mancanti (Medio Termine)**
|
||||
|
||||
1. **Modalità "History":**
|
||||
* **Obiettivo:** Permettere la visualizzazione e l'analisi dei dati di volo storici.
|
||||
* **Azioni:**
|
||||
* Definire l'interfaccia utente per la selezione della data/ora o del volo.
|
||||
* Implementare la logica nel controller per interrogare `DataStorage`.
|
||||
* Adattare `MapCanvasManager` per visualizzare tracce storiche (potrebbe richiedere modifiche per non aspettarsi dati "live").
|
||||
* Considerare una funzionalità di "replay" della traccia.
|
||||
2. **Vista Tabellare:**
|
||||
* **Obiettivo:** Fornire una vista alternativa ai dati dei voli live.
|
||||
* **Azioni:**
|
||||
* Usare `ttk.Treeview` per mostrare i dati da `AppController._current_flights_to_display_gui` (o una sua copia gestita).
|
||||
* Implementare ordinamento e selezione. La selezione dovrebbe aggiornare il pannello dei dettagli e potenzialmente centrare la mappa.
|
||||
3. **Modalità "Live: Airport":**
|
||||
* **Obiettivo:** Fornire una vista focalizzata sull'attività di un aeroporto specifico.
|
||||
* **Azioni:**
|
||||
* **Definizione:** Scegliere se basarsi su un raggio, su dati specifici di arrivi/partenze (richiederebbe nuove fonti dati/API).
|
||||
* **Dati Aeroporti:** Valutare se integrare un database di aeroporti (es. da `pyflightdata` o file CSV).
|
||||
* Implementare la logica di filtraggio e visualizzazione.
|
||||
|
||||
**FASE 4: Funzionalità Avanzate e Rifinitura (Lungo Termine)**
|
||||
|
||||
1. **Visualizzazione Immagini Aerei e Link Esterni (completamento):**
|
||||
* In `FullFlightDetailsWindow`, implementare il caricamento effettivo delle immagini (es. tramite API di JetPhotos, FlightAware, ecc., o scraping se consentito) e rendere più robusta l'apertura dei link.
|
||||
2. **Ricerca e Filtri Avanzati:**
|
||||
* Funzionalità di ricerca voli per callsign, ICAO24, tipo di aereo.
|
||||
* Filtri sulla mappa (es. per altitudine, velocità, tipo di aereo).
|
||||
3. **Configurazioni Utente:**
|
||||
* Possibilità di cambiare il map provider (se ne verranno aggiunti altri).
|
||||
* Opzioni per la gestione della cache dei tile (pulizia, dimensione massima).
|
||||
* Personalizzazione dell'aspetto delle tracce (colore, spessore - se `TRACK_LINE_WIDTH` diventa configurabile).
|
||||
4. **Test Approfonditi:**
|
||||
* Scrivere test unitari per le logiche di calcolo in `map_utils`, per i data adapter, e per la gestione del DB.
|
||||
* Test di integrazione per i flussi principali.
|
||||
5. **Documentazione e Packaging:**
|
||||
* Migliorare docstring e commenti.
|
||||
* Creare un manuale utente.
|
||||
* Preparare per la distribuzione (es. PyInstaller, cx_Freeze).
|
||||
|
||||
Questo è un primo abbozzo. Sono pronto a discutere ogni punto e ad integrare le tue idee. In particolare, vorrei capire meglio quali sono le tue priorità e le funzionalità che ritieni più importanti da sviluppare o migliorare nel breve termine, dopo la stabilizzazione iniziale.
|
||||
|
||||
Cosa ne pensi? Quali sono le tue idee o le aree su cui vorresti concentrarci?
|
||||
|
||||
Loading…
Reference in New Issue
Block a user