Files
OpenCore-Legacy-Patcher/resources/logging_handler.py
2023-02-05 16:31:49 -07:00

146 lines
4.7 KiB
Python

import logging
import sys
import threading
from pathlib import Path
class InitializeLoggingSupport:
"""
Initialize logging framework for program
Primary responsibilities:
- Determine where to store log file
- Clean log file if it's near the max file size
- Initialize logging framework configuration
- Implement custom traceback handler
- Implement error handling for file write
Usage:
>>> from resources.logging_handler import InitializeLoggingSupport
>>> InitializeLoggingSupport()
"""
def __init__(self) -> None:
self.log_filename: str = "OpenCore-Patcher.log"
self.log_filepath: Path = None
self.original_excepthook: sys = sys.excepthook
self.original_thread_excepthook: threading = threading.excepthook
self.max_file_size: int = 1024 * 1024 * 10 # 10 MB
self.file_size_redline: int = 1024 * 1024 * 9 # 9 MB, when to start cleaning log file
self._initialize_logging_path()
self._clean_log_file()
self._attempt_initialize_logging_configuration()
self._implement_custom_traceback_handler()
def __del__(self):
self._restore_original_excepthook()
def _initialize_logging_path(self):
"""
Initialize logging framework storage path
"""
self.log_filepath = Path(f"~/Library/Logs/{self.log_filename}").expanduser()
if not self.log_filepath.parent.exists():
# Likely in an installer environment, store in /Users/Shared
self.log_filepath = Path("/Users/Shared") / self.log_filename
print("- Initializing logging framework...")
print(f" - Log file: {self.log_filepath}")
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 not self.log_filepath.exists():
return
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")
try:
if backup_log_filepath.exists():
backup_log_filepath.unlink()
# Rename current log file to backup log file
self.log_filepath.rename(backup_log_filepath)
except Exception as e:
print(f"- Failed to clean log file: {e}")
def _initialize_logging_configuration(self, log_to_file: bool = True):
"""
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(stream = sys.stdout),
logging.FileHandler(self.log_filepath) if log_to_file is True else logging.NullHandler()
],
)
logging.getLogger().setLevel(logging.INFO)
logging.getLogger().handlers[0].setFormatter(logging.Formatter("%(message)s"))
logging.getLogger().handlers[1].maxBytes = self.max_file_size
def _attempt_initialize_logging_configuration(self):
"""
Attempt to initialize logging framework configuration
If we fail to initialize the logging framework, we will disable logging to file
"""
try:
self._initialize_logging_configuration()
except Exception as e:
print(f"- Failed to initialize logging framework: {e}")
print("- Retrying without logging to file...")
self._initialize_logging_configuration(log_to_file=False)
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
def _restore_original_excepthook(self):
"""
Restore original traceback handlers
"""
sys.excepthook = self.original_excepthook
threading.excepthook = self.original_thread_excepthook