diff --git a/CHANGELOG.md b/CHANGELOG.md index 648d82f26..7c7517f5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,8 @@ - Skips calls to Apple's now defunct Developer Portal API - Support local loose matching when no network connection is available - Implemented logging framework usage for more reliable logging - - Logs are stored under `~/OpenCore-Patcher-vX.Y.Z.log` + - Logs are stored under `~/OpenCore-Patcher.log` + - Subsequent runs are appended to the log, allowing for easy debugging - Implemented new network_handler.py module - Allows for more reliable network calls and downloads - Better supports network timeouts and disconnects diff --git a/resources/gui/gui_main.py b/resources/gui/gui_main.py index 165367b19..aedde9ea2 100644 --- a/resources/gui/gui_main.py +++ b/resources/gui/gui_main.py @@ -41,7 +41,7 @@ class wx_python_gui: self.hyperlink_colour = (25, 179, 231) # Backup stdout for usage with wxPython - self.stock_stream = logging.getLogger().handlers[1].stream + self.stock_stream = logging.getLogger().handlers[0].stream current_uid = os.getuid() @@ -108,7 +108,7 @@ class wx_python_gui: def reset_window(self): self.frame.DestroyChildren() self.frame.SetSize(self.WINDOW_WIDTH_MAIN, self.WINDOW_HEIGHT_MAIN) - logging.getLogger().handlers[1].stream = self.stock_stream + logging.getLogger().handlers[0].stream = self.stock_stream self.reset_frame_modal() # Re-enable sleep if we failed to do so before returning to the main menu @@ -707,7 +707,7 @@ class wx_python_gui: self.stdout_text.SetValue("") # Set StreamHandler to redirect stdout to textbox - logging.getLogger().handlers[1].stream = menu_redirect.RedirectText(self.stdout_text, False) + logging.getLogger().handlers[0].stream = menu_redirect.RedirectText(self.stdout_text, False) self.return_to_main_menu = wx.Button(self.frame_modal, label="Return to Main Menu") self.return_to_main_menu.SetPosition( @@ -735,7 +735,7 @@ class wx_python_gui: self.build_opencore.Bind(wx.EVT_BUTTON, self.install_menu) # Reset stdout - logging.getLogger().handlers[1].stream = self.stock_stream + logging.getLogger().handlers[0].stream = self.stock_stream # Throw popup asking to install OpenCore self.dialog = wx.MessageDialog( @@ -966,9 +966,9 @@ class wx_python_gui: self.frame_modal.SetSize(-1, self.stdout_text.GetPosition().y + self.stdout_text.GetSize().height + 40) self.frame_modal.ShowWindowModal() - logging.getLogger().handlers[1].stream = menu_redirect.RedirectText(self.stdout_text, False) + logging.getLogger().handlers[0].stream = menu_redirect.RedirectText(self.stdout_text, False) result = install.tui_disk_installation(self.constants).install_opencore(partition) - logging.getLogger().handlers[1].stream = self.stock_stream + logging.getLogger().handlers[0].stream = self.stock_stream self.return_to_main_menu = wx.Button(self.frame_modal, label="Return to Main Menu") self.return_to_main_menu.SetPosition( @@ -1392,7 +1392,7 @@ class wx_python_gui: self.frame_modal.SetSize(self.WINDOW_WIDTH_BUILD, self.return_to_main_menu.GetPosition().y + self.return_to_main_menu.GetSize().height + 40) - logging.getLogger().handlers[1].stream = menu_redirect.RedirectText(self.text_box, True) + logging.getLogger().handlers[0].stream = menu_redirect.RedirectText(self.text_box, True) self.frame_modal.ShowWindowModal() wx.GetApp().Yield() try: @@ -1400,7 +1400,7 @@ class wx_python_gui: except Exception as e: self.text_box.AppendText(f"- An internal error occurred while running the Root Patcher:\n{str(e)}") pass - logging.getLogger().handlers[1].stream = self.stock_stream + logging.getLogger().handlers[0].stream = self.stock_stream if self.constants.root_patcher_succeeded is True: logging.info("- Root Patcher finished successfully") if self.constants.needs_to_open_preferences is True: @@ -1501,7 +1501,7 @@ class wx_python_gui: self.frame_modal.SetSize(-1, self.return_to_main_menu.GetPosition().y + self.return_to_main_menu.GetSize().height + 40) # Start reverting root patches - logging.getLogger().handlers[1].stream = menu_redirect.RedirectText(self.text_box, True) + logging.getLogger().handlers[0].stream = menu_redirect.RedirectText(self.text_box, True) wx.GetApp().Yield() self.frame_modal.ShowWindowModal() while self.is_unpack_finished() is False: @@ -1511,7 +1511,7 @@ class wx_python_gui: except Exception as e: self.text_box.AppendText(f"- An internal error occurred while running the Root Patcher:\n{str(e)}") pass - logging.getLogger().handlers[1].stream = self.stock_stream + logging.getLogger().handlers[0].stream = self.stock_stream if self.constants.root_patcher_succeeded is True: logging.info("- Root Patcher finished successfully") self.reboot_system(message="Root Patcher finished successfully\nWould you like to reboot now?") diff --git a/resources/logging_handler.py b/resources/logging_handler.py new file mode 100644 index 000000000..54b3ee182 --- /dev/null +++ b/resources/logging_handler.py @@ -0,0 +1,95 @@ +import logging +import sys +import threading +from pathlib import Path + + +class InitializeLoggingSupport: + + + def __init__(self) -> None: + self.log_filename: str = f"OpenCore-Patcher.log" + self.log_filepath: Path = None + + self.max_file_size: int = 1024 * 1024 * 10 + self.file_size_redline: int = 1024 * 1024 * 9 # When to start cleaning log file + + self._initialize_logging_path() + self._clean_log_file() + self._initialize_logging_configuration() + self._implement_custom_traceback_handler() + + + def _initialize_logging_path(self): + """ + Initialize logging framework storage path + """ + + self.log_filepath = Path(f"~/Library/Logs/{self.log_filename}").expanduser() + + if self.log_filepath.parent.exists(): + return + + # Likely in an installer environment, store in /Users/Shared + self.log_filepath = Path("/Users/Shared") / self.log_filename + + + def _clean_log_file(self): + """ + Determine if log file should be cleaned + + We check if we're near the max file size, and if so, we clean the log file + """ + + if self.log_filepath.stat().st_size < self.file_size_redline: + return + + # Check if backup log file exists + backup_log_filepath = self.log_filepath.with_suffix(".old.log") + if backup_log_filepath.exists(): + backup_log_filepath.unlink() + + # Rename current log file to backup log file + self.log_filepath.rename(backup_log_filepath) + + + def _initialize_logging_configuration(self): + """ + Initialize logging framework configuration + + StreamHandler's format is used to mimic the default behavior of print() + While FileHandler's format is for more in-depth logging + """ + + logging.basicConfig( + level=logging.NOTSET, + format="%(asctime)s - %(filename)s (%(lineno)d): %(message)s", + handlers=[ + logging.StreamHandler(), + logging.FileHandler(self.log_filepath), + ], + ) + logging.getLogger().setLevel(logging.INFO) + logging.getLogger().handlers[0].setFormatter(logging.Formatter("%(message)s")) + logging.getLogger().handlers[1].maxBytes = self.max_file_size + + + def _implement_custom_traceback_handler(self): + """ + Reroute traceback to logging module + """ + + def custom_excepthook(type, value, tb): + """ + Reroute traceback in main thread to logging module + """ + logging.error("Uncaught exception in main thread", exc_info=(type, value, tb)) + + def custom_thread_excepthook(args): + """ + Reroute traceback in spawned thread to logging module + """ + logging.error("Uncaught exception in spawned thread", exc_info=(args)) + + sys.excepthook = custom_excepthook + threading.excepthook = custom_thread_excepthook \ No newline at end of file diff --git a/resources/main.py b/resources/main.py index 0ff3464b6..de1649fda 100644 --- a/resources/main.py +++ b/resources/main.py @@ -1,22 +1,35 @@ # Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk -import subprocess import sys -from pathlib import Path import time -import threading import logging import threading +import subprocess +from pathlib import Path -from resources import cli_menu, constants, utilities, device_probe, os_probe, defaults, arguments, install, tui_helpers, reroute_payloads, commit_info -from resources.build import build from data import model_array +from resources.build import build +from resources import ( + cli_menu, + constants, + utilities, + device_probe, + os_probe, + defaults, + arguments, + install, + tui_helpers, + reroute_payloads, + commit_info, + logging_handler +) + class OpenCoreLegacyPatcher: def __init__(self, launch_gui=False): self.constants = constants.Constants() self.constants.wxpython_variant = launch_gui - self.initialize_logging() + logging_handler.InitializeLoggingSupport() logging.info(f"- Loading OpenCore Legacy Patcher v{self.constants.patcher_version}...") @@ -29,40 +42,6 @@ class OpenCoreLegacyPatcher: else: self.main_menu() - def initialize_logging(self): - LOG_FILENAME = f"OpenCore-Patcher-v{self.constants.patcher_version}.log" - LOG_FILEPATH = Path(f"~/Library/Logs/{LOG_FILENAME}").expanduser() - - if not LOG_FILEPATH.parent.exists(): - # Likely in an installer environment, store in /Users/Shared - LOG_FILEPATH = Path("/Users/Shared") / LOG_FILENAME - - logging.basicConfig( - level=logging.NOTSET, - format="%(asctime)s - %(filename)s (%(lineno)d): %(message)s", - handlers=[ - logging.FileHandler(LOG_FILEPATH), - logging.StreamHandler(), - ], - ) - logging.getLogger().handlers[1].setFormatter(logging.Formatter("%(message)s")) - logging.getLogger().setLevel(logging.INFO) - logging.getLogger().handlers[1].maxBytes = 1024 * 1024 * 10 - - self.implement_custom_traceback_handler() - - - def implement_custom_traceback_handler(self): - # Reroute traceback to logging - def custom_excepthook(type, value, tb): - logging.error("Uncaught exception in main thread", exc_info=(type, value, tb)) - - def custom_thread_excepthook(args): - logging.error("Uncaught exception in spawned thread", exc_info=(args)) - - sys.excepthook = custom_excepthook - threading.excepthook = custom_thread_excepthook - def generate_base_data(self): self.constants.detected_os = os_probe.detect_kernel_major()