diff --git a/renumics/spotlight/app.py b/renumics/spotlight/app.py index 2425ca79..d39ea2ab 100644 --- a/renumics/spotlight/app.py +++ b/renumics/spotlight/app.py @@ -205,12 +205,12 @@ def _() -> None: self.include_router(plugin_api.router, prefix="/api/plugins") @self.exception_handler(Exception) - async def _(_: Request, e: Exception) -> JSONResponse: + async def _(request: Request, e: Exception) -> JSONResponse: if settings.verbose: logger.exception(e) else: logger.info(e) - emit_exception_event() + emit_exception_event(request.url.path) class_name = type(e).__name__ title = re.sub(r"([a-z])([A-Z])", r"\1 \2", class_name) return JSONResponse( @@ -219,11 +219,12 @@ async def _(_: Request, e: Exception) -> JSONResponse: ) @self.exception_handler(Problem) - async def _(_: Request, problem: Problem) -> JSONResponse: + async def _(request: Request, problem: Problem) -> JSONResponse: if settings.verbose: logger.exception(problem) else: logger.info(problem) + emit_exception_event(request.url.path) return JSONResponse( { "title": problem.title, diff --git a/renumics/spotlight/reporting.py b/renumics/spotlight/reporting.py index 787ec3c1..84031fe0 100644 --- a/renumics/spotlight/reporting.py +++ b/renumics/spotlight/reporting.py @@ -7,8 +7,10 @@ import sys import threading import time +import traceback from functools import wraps from os import environ +from pathlib import Path from typing import Any, Callable, Dict, Optional, Union from uuid import uuid4 @@ -171,14 +173,37 @@ def emit_exit_event() -> None: report_event({"type": "spotlight_exit"}) -def emit_exception_event() -> None: +def _sanitize_traceback_exception(exc: traceback.TracebackException) -> None: + spotlight_basepath = Path(__file__).parent.parent.absolute() + spotlight_frames: list = [] + for frame in exc.stack: + filename = Path(frame.filename) + try: + frame.filename = str(filename.relative_to(spotlight_basepath)) + except ValueError: + pass + else: + spotlight_frames.append(frame) + exc.stack = traceback.StackSummary.from_list(spotlight_frames) + if exc.__cause__ is not None: + _sanitize_traceback_exception(exc.__cause__) + if exc.__context__ is not None: + _sanitize_traceback_exception(exc.__context__) + + +def emit_exception_event(path: Optional[str] = None) -> None: """ Emit an exception event. """ - ex_type, ex_value, _ = sys.exc_info() - detail = str(ex_value) - if ex_type is not None: - detail = f"{ex_type.__name__}: {ex_value}" + _, exc, _ = sys.exc_info() + if exc is None: + return + traceback_exc = traceback.TracebackException.from_exception(exc) + _sanitize_traceback_exception(traceback_exc) + + if path: + detail = f"Path: {path}\n" + "\n\n".join(traceback_exc.format()) + report_event( { "type": "spotlight_exception",