Skip to content

Commit 8d26c8c

Browse files
authored
Feat: Log exception stack traces from executor tasks to API console (#825)
* Log exception stack traces from executor tasks to API console * PR feedback
1 parent 81ab4dd commit 8d26c8c

File tree

3 files changed

+25
-3
lines changed

3 files changed

+25
-3
lines changed

web/server/console.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import asyncio
44
import json
5+
import traceback
56
import typing as t
67
import unittest
78

@@ -100,3 +101,14 @@ def log_test_results(
100101
def log_success(self, msg: str) -> None:
101102
self.queue.put_nowait(self._make_event(msg))
102103
self.stop_snapshot_progress()
104+
105+
def log_exception(self, exc: Exception) -> None:
106+
"""Log an exception."""
107+
self.queue.put_nowait(
108+
self._make_event(
109+
{"details": str(exc), "traceback": traceback.format_exc()}, event="errors", ok=False
110+
)
111+
)
112+
113+
114+
api_console = ApiConsole()

web/server/main.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@
66
from fastapi.staticfiles import StaticFiles
77

88
from web.server.api.endpoints import api_router
9-
from web.server.console import ApiConsole
9+
from web.server.console import api_console
1010
from web.server.watcher import watch_project
1111

1212
app = FastAPI()
13-
api_console = ApiConsole()
1413

1514
app.include_router(api_router, prefix="/api")
1615
WEB_DIRECTORY = pathlib.Path(__file__).parent.parent

web/server/utils.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import asyncio
4+
import functools
45
import io
56
import traceback
67
import typing as t
@@ -13,6 +14,7 @@
1314
from starlette.status import HTTP_404_NOT_FOUND, HTTP_422_UNPROCESSABLE_ENTITY
1415

1516
from sqlmesh.core.context import Context
17+
from web.server.console import api_console
1618
from web.server.settings import get_context
1719

1820
R = t.TypeVar("R")
@@ -26,8 +28,17 @@ def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
2628

2729
async def run_in_executor(func: t.Callable[..., R], *args: t.Any) -> R:
2830
"""Run in the default loop's executor"""
31+
32+
@functools.wraps(func)
33+
def func_wrapper() -> R:
34+
try:
35+
return func(*args)
36+
except Exception as e:
37+
api_console.log_exception(e)
38+
raise e
39+
2940
loop = asyncio.get_running_loop()
30-
return await loop.run_in_executor(None, func, *args)
41+
return await loop.run_in_executor(None, func_wrapper)
3142

3243

3344
def validate_path(

0 commit comments

Comments
 (0)