From b6f3843e9f7b288856295c2e0ab44efd4eeea1f6 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Wed, 16 Apr 2025 10:34:30 +0200 Subject: [PATCH] Improve handling of fatal exceptions (as showing the GUI, even on the main thread, can fail), making sure that the error is furthermore logged or at least written to stderr --- src/serena/agent.py | 26 +++++++++++++++++++++++++- src/serena/agno.py | 5 ++--- src/serena/mcp.py | 5 ++--- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/serena/agent.py b/src/serena/agent.py index b755bd2f..d9a78d57 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -6,6 +6,7 @@ import inspect import json import os import platform +import sys import traceback from abc import ABC from collections import defaultdict @@ -16,6 +17,7 @@ from typing import TYPE_CHECKING, Any, Self, TypeVar, Union, cast import yaml from sensai.util import logging +from sensai.util.logging import FallbackHandler from sensai.util.string import ToStringMixin, dict_string from multilspy import SyncLanguageServer @@ -40,6 +42,27 @@ TTool = TypeVar("TTool", bound="Tool") SUCCESS_RESULT = "OK" +def show_fatal_exception_safe(e: Exception) -> None: + """ + Shows the given exception in the GUI log viewer on the main thread and ensures that the exception is logged or at + least printed to stderr. + """ + # Make sure the error is logged (adding a fallback handler which writes to stderr in case there is no other handler) + fallback_handler = FallbackHandler(logging.StreamHandler(sys.stderr)) + Logger.root.addHandler(fallback_handler) + log.error(f"Fatal exception: {e}", exc_info=e) + + # attempt to show the error in the GUI + try: + # NOTE: The import can fail on macOS if Tk is not available (depends on Python interpreter installation, which uv + # used as a base); while tkinter as such is always available, its dependencies can be unavailable on macOS. + from serena.gui_log_viewer import show_fatal_exception + + show_fatal_exception(e) + except: + pass + + class SerenaConfigError(Exception): pass @@ -171,7 +194,8 @@ class SerenaAgent: if platform.system() == "Darwin": log.warning("GUI log window is not supported on macOS") else: - # even importing on macOS may fail since tkinter is not available by default + # even importing on macOS may fail if tkinter dependencies are unavailable (depends on Python interpreter installation + # which uv used as a base, unfortunately) from serena.gui_log_viewer import GuiLogViewer, GuiLogViewerHandler log_level = self.serena_config.gui_log_window_level diff --git a/src/serena/agno.py b/src/serena/agno.py index 2af283db..3157229e 100644 --- a/src/serena/agno.py +++ b/src/serena/agno.py @@ -15,8 +15,7 @@ from dotenv import load_dotenv from sensai.util.logging import LogTime from serena import serena_root_path -from serena.agent import SerenaAgent, Tool -from serena.gui_log_viewer import show_fatal_exception +from serena.agent import SerenaAgent, Tool, show_fatal_exception_safe log = logging.getLogger(__name__) @@ -92,7 +91,7 @@ class SerenaAgnoAgentProvider: try: serena_agent = SerenaAgent(project_file) except Exception as e: - show_fatal_exception(e) + show_fatal_exception_safe(e) raise # Even though we don't want to keep history between sessions, diff --git a/src/serena/mcp.py b/src/serena/mcp.py index d55a5b1c..2de742af 100644 --- a/src/serena/mcp.py +++ b/src/serena/mcp.py @@ -15,8 +15,7 @@ from mcp.server.fastmcp.utilities.func_metadata import func_metadata from sensai.util import logging from sensai.util.helper import mark_used -from serena.agent import SerenaAgent, Tool -from serena.gui_log_viewer import show_fatal_exception +from serena.agent import SerenaAgent, Tool, show_fatal_exception_safe log = logging.getLogger(__name__) LOG_FORMAT = "%(levelname)-5s %(asctime)-15s %(name)s:%(funcName)s:%(lineno)d - %(message)s" @@ -100,7 +99,7 @@ def create_mcp_server() -> FastMCP: # project_activation_callback=update_tools ) except Exception as e: - show_fatal_exception(e) + show_fatal_exception_safe(e) raise @asynccontextmanager