diff options
author | Trygve Aaberge <trygveaa@gmail.com> | 2023-02-02 21:53:40 +0100 |
---|---|---|
committer | Trygve Aaberge <trygveaa@gmail.com> | 2024-02-18 11:32:53 +0100 |
commit | a1c4b14c8f504b39cfadd78a9010c3ed6495e123 (patch) | |
tree | f4ad95fdf5eb6d05d27e9f1d36736048a5f4ee54 | |
parent | b91385fe3d3538704deb06bb187dc48efa87e34b (diff) | |
download | wee-slack-a1c4b14c8f504b39cfadd78a9010c3ed6495e123.tar.gz |
Record uncaught errors and add command to display them
-rw-r--r-- | slack/commands.py | 42 | ||||
-rw-r--r-- | slack/error.py | 29 | ||||
-rw-r--r-- | slack/log.py | 4 | ||||
-rw-r--r-- | slack/python_compatibility.py | 8 | ||||
-rw-r--r-- | slack/shared.py | 2 | ||||
-rw-r--r-- | slack/task.py | 6 |
6 files changed, 79 insertions, 12 deletions
diff --git a/slack/commands.py b/slack/commands.py index 60fd5d9..5e6e40c 100644 --- a/slack/commands.py +++ b/slack/commands.py @@ -8,8 +8,9 @@ from typing import Callable, Dict, List, Optional, Tuple import weechat +from slack.error import UncaughtError from slack.log import print_error -from slack.python_compatibility import removeprefix, removesuffix +from slack.python_compatibility import format_exception, removeprefix, removesuffix from slack.shared import shared from slack.slack_conversation import ( SlackConversation, @@ -261,7 +262,14 @@ def command_slack_workspace_del( ) -@weechat_command("tasks|buffer") +def print_uncaught_error(error: UncaughtError, detailed: bool = False): + weechat.prnt("", f" {error.id} ({error.time}): {error.exception}") + if detailed: + for line in format_exception(error.exception): + weechat.prnt("", f" {line}") + + +@weechat_command("tasks|buffer|errors|error", split_all_args=True) def command_slack_debug( buffer: str, args: List[str], options: Dict[str, Optional[str]] ): @@ -274,6 +282,36 @@ def command_slack_debug( conversation = get_conversation_from_buffer_pointer(buffer) if conversation: weechat.prnt("", f"Conversation id: {conversation.id}") + elif args[0] == "errors": + num_arg = int(args[1]) if len(args) > 1 and args[1].isdecimal() else 5 + num = min(num_arg, len(shared.uncaught_errors)) + weechat.prnt("", f"Last {num} errors:") + for error in shared.uncaught_errors[-num:]: + print_uncaught_error(error) + elif args[0] == "error": + if len(args) > 1: + if args[1].isdecimal() and args[1] != "0": + num = int(args[1]) + if num > len(shared.uncaught_errors): + print_error( + f"Only {len(shared.uncaught_errors)} error(s) have occurred" + ) + return + error = shared.uncaught_errors[-num] + else: + errors = [e for e in shared.uncaught_errors if e.id == args[1]] + if not errors: + print_error(f"Error {args[1]} not found") + return + error = errors[0] + weechat.prnt("", f"Error {error.id}:") + elif not shared.uncaught_errors: + weechat.prnt("", "No errors have occurred") + return + else: + error = shared.uncaught_errors[-1] + weechat.prnt("", "Last error:") + print_uncaught_error(error, True) def completion_slack_workspaces_cb( diff --git a/slack/error.py b/slack/error.py index a92626d..8817759 100644 --- a/slack/error.py +++ b/slack/error.py @@ -1,6 +1,11 @@ from __future__ import annotations +from dataclasses import dataclass, field +from datetime import datetime from typing import TYPE_CHECKING, Dict, Mapping, Sequence, Union +from uuid import uuid4 + +from slack.shared import shared if TYPE_CHECKING: from slack_api.slack_common import SlackErrorResponse @@ -55,18 +60,32 @@ class SlackError(Exception): self.error = error -def format_exception(e: BaseException): +@dataclass +class UncaughtError: + id: str = field(init=False) + exception: BaseException + + def __post_init__(self): + self.id = str(uuid4()) + self.time = datetime.now() + + +def store_and_format_exception(e: BaseException): + uncaught_error = UncaughtError(e) + shared.uncaught_errors.append(uncaught_error) + stack_msg = f"(run /slack debug error {uncaught_error.id} for stack trace)" + if isinstance(e, HttpError): return ( f"Error calling URL {e.url}: return code: {e.return_code}, " - f"http status code: {e.http_status_code}, error: {e.error}" + f"http status code: {e.http_status_code}, error: {e.error} {stack_msg}" ) elif isinstance(e, SlackApiError): return ( f"Error from Slack API method {e.method} with params {e.params} for workspace " - f"{e.workspace.name}: {e.response}" + f"{e.workspace.name}: {e.response} {stack_msg}" ) elif isinstance(e, SlackError): - return f"Error occurred in workspace {e.workspace.name}: {e.error}" + return f"Error occurred in workspace {e.workspace.name}: {e.error} {stack_msg}" else: - return f"Unknown error occurred: {e.__class__.__name__}: {e}" + return f"Unknown error occurred: {e.__class__.__name__}: {e} {stack_msg}" diff --git a/slack/log.py b/slack/log.py index b132c0e..4b8bbe1 100644 --- a/slack/log.py +++ b/slack/log.py @@ -5,7 +5,7 @@ from typing import Set import weechat -from slack.error import format_exception +from slack.error import store_and_format_exception from slack.shared import shared printed_exceptions: Set[BaseException] = set() @@ -27,7 +27,7 @@ def print_error(message: str): def print_exception_once(e: BaseException): if e not in printed_exceptions: - print_error(format_exception(e)) + print_error(store_and_format_exception(e)) printed_exceptions.add(e) diff --git a/slack/python_compatibility.py b/slack/python_compatibility.py index 1590894..e96b8af 100644 --- a/slack/python_compatibility.py +++ b/slack/python_compatibility.py @@ -1,3 +1,7 @@ +import traceback +from typing import List + + # Copied from https://peps.python.org/pep-0616/ for support for Python < 3.9 def removeprefix(self: str, prefix: str) -> str: if self.startswith(prefix): @@ -12,3 +16,7 @@ def removesuffix(self: str, suffix: str) -> str: return self[: -len(suffix)] else: return self[:] + + +def format_exception(exc: BaseException) -> List[str]: + return traceback.format_exception(type(exc), exc, exc.__traceback__) diff --git a/slack/shared.py b/slack/shared.py index 31ff748..8dbac1a 100644 --- a/slack/shared.py +++ b/slack/shared.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Callable, Dict, List, Union if TYPE_CHECKING: from slack.config import SlackConfig + from slack.error import UncaughtError from slack.slack_workspace import SlackWorkspace from slack.task import Future, Task @@ -22,6 +23,7 @@ class Shared: self.active_futures: Dict[str, Future[object]] = {} self.workspaces: Dict[str, SlackWorkspace] = {} self.config: SlackConfig + self.uncaught_errors: List[UncaughtError] = [] shared = Shared() diff --git a/slack/task.py b/slack/task.py index b812387..3fafa2e 100644 --- a/slack/task.py +++ b/slack/task.py @@ -20,7 +20,7 @@ from uuid import uuid4 import weechat -from slack.error import format_exception +from slack.error import store_and_format_exception from slack.log import print_error from slack.shared import shared from slack.util import get_callback_name @@ -212,7 +212,7 @@ def task_runner(task: Task[Any]): if not task.exception_read(): print_error( f"{task} was never awaited and failed with: " - f"{format_exception(exception)}" + f"{store_and_format_exception(exception)}" ) failed_tasks.clear() @@ -226,7 +226,7 @@ def create_task(coroutine: Coroutine[Future[Any], Any, T]) -> Task[T]: def _async_task_done(task: Task[object]): exception = task.exception() if exception: - print_error(f"{task} failed with: {format_exception(exception)}") + print_error(f"{task} failed with: {store_and_format_exception(exception)}") def run_async(coroutine: Coroutine[Future[Any], Any, Any]) -> None: |