|
| 1 | +import logging |
| 2 | +import sys |
| 3 | +import threading |
| 4 | +from typing import TYPE_CHECKING |
| 5 | + |
| 6 | +from posthog.exception_utils import exceptions_from_error_tuple |
| 7 | + |
| 8 | +if TYPE_CHECKING: |
| 9 | + from posthog.client import Client |
| 10 | + |
| 11 | + |
| 12 | +class ExceptionCapture: |
| 13 | + # TODO: Add client side rate limiting to prevent spamming the server with exceptions |
| 14 | + |
| 15 | + log = logging.getLogger("posthog") |
| 16 | + |
| 17 | + def __init__(self, client: "Client"): |
| 18 | + self.client = client |
| 19 | + self.original_excepthook = sys.excepthook |
| 20 | + sys.excepthook = self.exception_handler |
| 21 | + threading.excepthook = self.thread_exception_handler |
| 22 | + |
| 23 | + def exception_handler(self, exc_type, exc_value, exc_traceback): |
| 24 | + # don't affect default behaviour. |
| 25 | + self.capture_exception(exc_type, exc_value, exc_traceback) |
| 26 | + self.original_excepthook(exc_type, exc_value, exc_traceback) |
| 27 | + |
| 28 | + def thread_exception_handler(self, args): |
| 29 | + self.capture_exception(args.exc_type, args.exc_value, args.exc_traceback) |
| 30 | + |
| 31 | + def capture_exception(self, exc_type, exc_value, exc_traceback): |
| 32 | + try: |
| 33 | + # if hasattr(sys, "ps1"): |
| 34 | + # # Disable the excepthook for interactive Python shells |
| 35 | + # return |
| 36 | + |
| 37 | + # Format stack trace like sentry |
| 38 | + all_exceptions_with_trace = exceptions_from_error_tuple((exc_type, exc_value, exc_traceback)) |
| 39 | + |
| 40 | + properties = { |
| 41 | + "$exception_type": all_exceptions_with_trace[0].get("type"), |
| 42 | + "$exception_message": all_exceptions_with_trace[0].get("value"), |
| 43 | + "$exception_list": all_exceptions_with_trace, |
| 44 | + # TODO: Can we somehow get distinct_id from context here? Stateless lib makes this much harder? 😅 |
| 45 | + # '$exception_personURL': f'{self.client.posthog_host}/project/{self.client.token}/person/{self.client.get_distinct_id()}' |
| 46 | + } |
| 47 | + |
| 48 | + # TODO: What distinct id should we attach these server-side exceptions to? |
| 49 | + # Any heuristic seems prone to errors - how can we know if exception occurred in the context of a user that captured some other event? |
| 50 | + |
| 51 | + self.client.capture("python-exceptions", "$exception", properties=properties) |
| 52 | + except Exception as e: |
| 53 | + self.log.exception(f"Failed to capture exception: {e}") |
0 commit comments