From 4b233baab65d48b8d292ee73a5570275880f6b44 Mon Sep 17 00:00:00 2001 From: AKOMI <58747243+komima@users.noreply.github.com> Date: Mon, 20 May 2024 17:36:34 +0300 Subject: [PATCH] feat: allow custom logger name for slot exception handler --- CHANGELOG.md | 2 ++ tools/decorations.py | 47 ++++++++++++++++++++++++++++---------------- tools/messages.py | 12 ++++++++--- 3 files changed, 41 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f253f8..288d8f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Feat: Support custom logger names for `log_if_fails` decorator + ## [0.4.1] - 2024-2-1 - Fix: Fix submodule usage detection when installed as an embedded dependency diff --git a/tools/decorations.py b/tools/decorations.py index e6a47ef..5d0120f 100644 --- a/tools/decorations.py +++ b/tools/decorations.py @@ -3,35 +3,48 @@ __email__ = "info@gispo.fi" __revision__ = "$Format:%H$" -from typing import Any, Callable +from typing import Any, Callable, Optional from .exceptions import QgsPluginException from .i18n import tr -from .messages import MsgBar +from .messages import MessageBarLogger from .tasks import FunctionTask -def log_if_fails(fn: Callable) -> Callable: +def log_if_fails( + fn: Optional[Callable] = None, /, *, logger_name: str = __name__ +) -> Callable: """ Use this as a decorator with functions and methods that might throw uncaught exceptions. """ from functools import wraps - @wraps(fn) - def wrapper(*args: Any, **kwargs: Any) -> None: # noqa: ANN001 - try: - # Qt injects False into some signals - if args[1:] != (False,): - fn(*args, **kwargs) - else: - fn(*args[:-1], **kwargs) - except QgsPluginException as e: - MsgBar.exception(e, **e.bar_msg, stack_info=True) - except Exception as e: - MsgBar.exception(tr("Unhandled exception occurred"), e, stack_info=True) - - return wrapper + # caller is at depth 3 (MessageBarLogger log call, this function, actual call) + message_bar = MessageBarLogger(logger_name, stack_level=3) + + def decorator(fn: Callable) -> Callable: + @wraps(fn) + def wrapper(*args: Any, **kwargs: Any) -> None: # noqa: ANN001 + try: + # Qt injects False into some signals + if args[1:] != (False,): + fn(*args, **kwargs) + else: + fn(*args[:-1], **kwargs) + except QgsPluginException as e: + message_bar.exception(e, **e.bar_msg, stack_info=True) + except Exception as e: + message_bar.exception( + tr("Unhandled exception occurred"), e, stack_info=True + ) + + return wrapper + + if fn is None: + return decorator + + return decorator(fn) def taskify(fn: Callable) -> Callable: diff --git a/tools/messages.py b/tools/messages.py index 52f9f44..77dbe00 100644 --- a/tools/messages.py +++ b/tools/messages.py @@ -13,12 +13,12 @@ class MessageBarLogger: Setup with a logger name that has a message bar set. """ - def __init__(self, logger_name: str) -> None: + def __init__(self, logger_name: str, stack_level: int = 2) -> None: self._logger = logging.getLogger(logger_name) self._logger_kwargs: Dict[str, Any] = ( {} if sys.version_info.major == 3 and sys.version_info.minor < 8 - else {"stacklevel": 2} + else {"stacklevel": stack_level} ) def info( @@ -148,10 +148,16 @@ def exception( :param exc_info: Exception of handled exception for capturing traceback :param stack_info: Whether to include stack info """ - self._logger.exception( + # for some reason using logger.exception will essentially have extra stack level + # even though its not visible on the printed stack (possibly due to exception + # being a simple helper which calls error internally). use plain error here to + # have same effective stacklevel on both actual log records. possibly related + # https://github.com/python/cpython/issues/89334 has been fixed in 3.11+ + self._logger.error( str(message), extra=bar_msg(details, duration, success), stack_info=stack_info, + exc_info=True, **self._logger_kwargs, ) if details != "":