Skip to content

Commit

Permalink
Logging of Qt messages (#32)
Browse files Browse the repository at this point in the history
- open txt editor when critical error occurs
  • Loading branch information
andreasgriffin authored Nov 10, 2024
1 parent 9c98f0d commit 8fad701
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 16 deletions.
1 change: 1 addition & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"args": [
"-vvv",
"--ignore=coding_tests",
"--ignore=tools",
],
"console": "integratedTerminal",
// "justMyCode": false
Expand Down
74 changes: 69 additions & 5 deletions bitcoin_safe/logging_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@
import logging
import os
import platform
import shlex
import subprocess
import sys
import tempfile
from pathlib import Path

from bitcoin_safe import __version__

Expand All @@ -46,6 +50,43 @@ def remove_absolute_paths(line: str) -> str:
return line.replace(current_path, "")


def get_system_info_as_text() -> str:
if hasattr(platform, "freedesktop_os_release"):
os_release_info = platform.freedesktop_os_release()
distro_name = os_release_info.get("NAME", "Unknown")
distro_version = os_release_info.get("VERSION", "")
else:
distro_name = "Unknown"
distro_version = ""

body = "\n\nSystem Info:\n"
body += f"OS: {platform.platform()}\n"
body += f"Distribution: {distro_name} {distro_version}\n"
body += f"Python Version: {sys.version}\n"
body += f"Bitcoin Safe Version: {__version__}\n\n"
return body


def text_error_report(error_report: str, file_path: Path | None = None) -> str:
email = "[email protected]"
subject = f"Error report - Bitcoin Safe Version: {__version__}"
body = ""
if file_path:
body += f"You can see the full logfile at: {file_path}\n\n"

body += f"Please email this to: {email}\n\n"
body += f"{subject}\n\n"
body += f"""Error:
{error_report}
""".replace(
" ", ""
)

# Write additional system info if needed
body += get_system_info_as_text()
return body


def mail_error_repot(error_report: str) -> None:
email = "[email protected]"
subject = f"Error report - Bitcoin Safe Version: {__version__}"
Expand All @@ -55,11 +96,7 @@ def mail_error_repot(error_report: str) -> None:
" ", ""
)

# Write additional system info if needed
body += "\n\nSystem Info:\n"
body += f"OS: {platform.platform()}\n"
body += f"Python Version: {sys.version}\n"
body += f"Bitcoin Safe Version: {__version__}\n\n"
body += get_system_info_as_text()
return compose_email(email, subject, body)


Expand All @@ -86,3 +123,30 @@ def emit(self, record) -> None:

message = str(self.format(record))
mail_error_repot(message)


class OpenLogHandler(logging.Handler):
def __init__(self, file_path: Path, level=logging.CRITICAL) -> None:
super().__init__(level)
self.file_path = file_path

@staticmethod
def xdg_open_file(filename: Path):
system_name = platform.system()
if system_name == "Windows":
subprocess.call(shlex.split(f'start "" /max "{filename}"'), shell=True)
elif system_name == "Darwin": # macOS
subprocess.call(shlex.split(f'open "{filename}"'))
elif system_name == "Linux": # Linux
subprocess.call(shlex.split(f'xdg-open "{filename}"'))

def emit(self, record) -> None:

message = text_error_report(str(self.format(record)), file_path=self.file_path)

# Create a temporary file with a message to the user
with tempfile.NamedTemporaryFile(mode="w+", delete=False, suffix=".txt") as temp_file:
temp_file.write(message)
temp_file_path = temp_file.name

self.xdg_open_file(Path(temp_file_path))
33 changes: 28 additions & 5 deletions bitcoin_safe/logging_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,14 @@
from pathlib import Path

import appdirs
from PyQt6.QtCore import QtMsgType, qInstallMessageHandler

from bitcoin_safe import __version__
from bitcoin_safe.logging_handlers import MailHandler, RelativePathFormatter
from bitcoin_safe.logging_handlers import (
MailHandler,
OpenLogHandler,
RelativePathFormatter,
)


def setup_logging() -> None:
Expand All @@ -61,19 +66,20 @@ def setup_logging() -> None:
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(relative_path_formatter)

custom_handler = (
mail_handler = (
MailHandler()
) # Assuming MailHandler is correctly implemented in bitcoin_safe.logging_handlers
custom_handler.setLevel(logging.CRITICAL)
custom_handler.setFormatter(relative_path_formatter)
mail_handler.setLevel(logging.CRITICAL)
mail_handler.setFormatter(relative_path_formatter)
# Assuming 'must_include_exc_info' is handled internally in the MailHandler implementation

# Configuring loggers
root_logger = logging.getLogger()
root_logger.setLevel(logging.DEBUG)
root_logger.addHandler(console_handler)
root_logger.addHandler(file_handler)
root_logger.addHandler(custom_handler)
root_logger.addHandler(OpenLogHandler(file_path=log_file))
root_logger.addHandler(mail_handler)
root_logger.propagate = True

logger = logging.getLogger(__name__)
Expand All @@ -84,6 +90,23 @@ def handle_uncaught_exception(exc_type, exc_value, exc_traceback) -> None:

sys.excepthook = handle_uncaught_exception

def qt_message_handler(msg_type, context, message):
if msg_type == QtMsgType.QtDebugMsg:
logger.debug(message)
elif msg_type == QtMsgType.QtInfoMsg:
logger.info(message)
elif msg_type == QtMsgType.QtWarningMsg:
logger.warning(message)
elif msg_type == QtMsgType.QtCriticalMsg:
logger.error(message)
elif msg_type == QtMsgType.QtFatalMsg:
logger.critical(message)
else:
logger.error(message)

# Install the message handler as early as possible
qInstallMessageHandler(qt_message_handler)

logger.info(f"========================= Starting Bitcoin Safe ========================")
logger.info(f"Version: {__version__}")
logger.info(f"Python version: {sys.version}. On platform: {describe_os_version()}")
Expand Down
16 changes: 10 additions & 6 deletions tests/non_gui/test_mail_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,15 @@ def test_no_error_logging(caplog: LogCaptureFixture):


def test_exception_logging(caplog: LogCaptureFixture):
with patch("bitcoin_safe.logging_handlers.compose_email") as mock_compose_email:
mock_compose_email.return_value = "Mocked Function"
with patch("bitcoin_safe.logging_handlers.OpenLogHandler.emit") as mock_OpenLogHandler_emit:
mock_OpenLogHandler_emit.return_value = "Mocked OpenLogHandler.emit"
with patch("bitcoin_safe.logging_handlers.compose_email") as mock_compose_email:
mock_compose_email.return_value = "Mocked Function"

with caplog.at_level(logging.INFO):
logger.critical("this should compose an email", exc_info=sys.exc_info())
with caplog.at_level(logging.INFO):
logger.critical("this should compose an email", exc_info=sys.exc_info())

# Assert that the mocked function was called
mock_compose_email.assert_called_once()
# Assert that the mocked function was called
mock_compose_email.assert_called_once()
# Assert that the mocked function was called
mock_OpenLogHandler_emit.assert_called_once()

0 comments on commit 8fad701

Please sign in to comment.