aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTrygve Aaberge <trygveaa@gmail.com>2023-02-02 21:53:40 +0100
committerTrygve Aaberge <trygveaa@gmail.com>2024-02-18 11:32:53 +0100
commita1c4b14c8f504b39cfadd78a9010c3ed6495e123 (patch)
treef4ad95fdf5eb6d05d27e9f1d36736048a5f4ee54
parentb91385fe3d3538704deb06bb187dc48efa87e34b (diff)
downloadwee-slack-a1c4b14c8f504b39cfadd78a9010c3ed6495e123.tar.gz
Record uncaught errors and add command to display them
-rw-r--r--slack/commands.py42
-rw-r--r--slack/error.py29
-rw-r--r--slack/log.py4
-rw-r--r--slack/python_compatibility.py8
-rw-r--r--slack/shared.py2
-rw-r--r--slack/task.py6
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: