From bd14bafe30d6f0257d5395c153202d486395f7e2 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Thu, 18 Jul 2024 11:42:33 +0200 Subject: [PATCH] Group tool templating exceptions in sentry This will properly group tool templating exceptions. The added tags should make it easy to prioritize and distribute fixes. Also improves the exception if not using sentry: ``` ... Cheetah.Parser.ParseError: Some #directives are missing their corresponding #end ___ tag: for Line 3, column 4 Line|Cheetah Code ----|------------------------------------------------------------- 2 | moo 3 | ^ The above exception was the direct cause of the following exception: Traceback (most recent call last): File "/Users/mvandenb/src/galaxy/lib/galaxy/jobs/runners/__init__.py", line 297, in prepare_job job_wrapper.prepare() File "/Users/mvandenb/src/galaxy/lib/galaxy/jobs/__init__.py", line 1260, in prepare ) = tool_evaluator.build() File "/Users/mvandenb/src/galaxy/lib/galaxy/tools/evaluation.py", line 611, in build global_tool_logs(self._build_command_line, config_file, "Building Command Line", self.tool) File "/Users/mvandenb/src/galaxy/lib/galaxy/tools/evaluation.py", line 111, in global_tool_logs raise ToolTemplatingException( galaxy.tools.evaluation.ToolTemplatingException: Error occurred while building command line for tool 'cheetah_problem_syntax_error' ``` --- lib/galaxy/app.py | 23 ++++++++++++++++ lib/galaxy/tools/evaluation.py | 49 +++++++++++++++++++++++++--------- 2 files changed, 59 insertions(+), 13 deletions(-) diff --git a/lib/galaxy/app.py b/lib/galaxy/app.py index 7668fb54be6a..6972488a01c9 100644 --- a/lib/galaxy/app.py +++ b/lib/galaxy/app.py @@ -146,6 +146,7 @@ from galaxy.tools.cache import ToolCache from galaxy.tools.data import ToolDataTableManager from galaxy.tools.data_manager.manager import DataManagers +from galaxy.tools.evaluation import ToolTemplatingException from galaxy.tools.error_reports import ErrorReports from galaxy.tools.search import ToolBoxSearch from galaxy.tools.special_tools import load_lib_tools @@ -227,12 +228,34 @@ def configure_sentry_client(self): level=logging.INFO, # Capture info and above as breadcrumbs event_level=getattr(logging, event_level), # Send errors as events ) + + def before_send(event, hint): + if "exc_info" in hint: + exc_type, exc_value, tb = hint["exc_info"] + if isinstance(exc_value, ToolTemplatingException): + # We set a custom fingerprint that may look like: + # ["Error occurred while {action_str.lower()} for tool '{tool.id}'", + # "1.0", + # "cannot find 'file_name' while searching for 'species_chromosomes.file_name'"] + # If we don't do this issues are never properly grouped since by default the calling stack is inspected, + # and that is always unique in cheetah as it is dynamically generated. + event["fingerprint"] = [str(exc_value), str(exc_value.tool_version), str(exc_value.__cause__)] + event.setdefault("tags", {}).update( + { + "tool_is_latest": exc_value.is_latest, + "tool_id": exc_value.tool_id, + "tool_version": exc_value.tool_version, + } + ) + return event + self.sentry_client = sentry_sdk.init( self.config.sentry_dsn, release=f"{self.config.version_major}.{self.config.version_minor}", integrations=[sentry_logging], traces_sample_rate=self.config.sentry_traces_sample_rate, ca_certs=self.config.sentry_ca_certs, + before_send=before_send, ) diff --git a/lib/galaxy/tools/evaluation.py b/lib/galaxy/tools/evaluation.py index 07c04ab6055a..dcef764cac64 100644 --- a/lib/galaxy/tools/evaluation.py +++ b/lib/galaxy/tools/evaluation.py @@ -13,6 +13,7 @@ List, Optional, Union, + TYPE_CHECKING, ) from packaging.version import Version @@ -70,6 +71,9 @@ from galaxy.util.tree_dict import TreeDict from galaxy.work.context import WorkRequestContext +if TYPE_CHECKING: + from galaxy.tools import Tool + log = logging.getLogger(__name__) @@ -89,13 +93,27 @@ def add_error(self, file, phase, exception): global_tool_errors = ToolErrorLog() -def global_tool_logs(func, config_file, action_str): +class ToolTemplatingException(Exception): + + def __init__(self, *args: object, tool_id: str, tool_version: str, is_latest: bool) -> None: + super().__init__(*args) + self.tool_id = tool_id + self.tool_version = tool_version + self.is_latest = is_latest + + +def global_tool_logs(func, config_file: str, action_str: str, tool: "Tool"): try: return func() except Exception as e: # capture and log parsing errors global_tool_errors.add_error(config_file, action_str, e) - raise e + raise ToolTemplatingException( + f"Error occurred while {action_str.lower()} for tool '{tool.id}'", + tool_id=tool.id, + tool_version=tool.version, + is_latest=tool.is_latest_version, + ) from e DeferrableObjectsT = Union[ @@ -581,13 +599,18 @@ def build(self): """ config_file = self.tool.config_file global_tool_logs( - self._create_interactivetools_entry_points, config_file, "Building Interactive Tool Entry Points" + self._create_interactivetools_entry_points, config_file, "Building Interactive Tool Entry Points", self.tool + ) + global_tool_logs( + self._build_config_files, + config_file, + "Building Config Files", + self.tool, ) - global_tool_logs(self._build_config_files, config_file, "Building Config Files") - global_tool_logs(self._build_param_file, config_file, "Building Param File") - global_tool_logs(self._build_command_line, config_file, "Building Command Line") - global_tool_logs(self._build_version_command, config_file, "Building Version Command Line") - global_tool_logs(self._build_environment_variables, config_file, "Building Environment Variables") + global_tool_logs(self._build_param_file, config_file, "Building Param File", self.tool) + global_tool_logs(self._build_command_line, config_file, "Building Command Line", self.tool) + global_tool_logs(self._build_version_command, config_file, "Building Version Command Line", self.tool) + global_tool_logs(self._build_environment_variables, config_file, "Building Environment Variables", self.tool) return ( self.command_line, self.version_command_line, @@ -836,7 +859,7 @@ class PartialToolEvaluator(ToolEvaluator): def build(self): config_file = self.tool.config_file - global_tool_logs(self._build_environment_variables, config_file, "Building Environment Variables") + global_tool_logs(self._build_environment_variables, config_file, "Building Environment Variables", self.tool) return ( self.command_line, self.version_command_line, @@ -857,10 +880,10 @@ def execute_tool_hooks(self, inp_data, out_data, incoming): def build(self): config_file = self.tool.config_file - global_tool_logs(self._build_config_files, config_file, "Building Config Files") - global_tool_logs(self._build_param_file, config_file, "Building Param File") - global_tool_logs(self._build_command_line, config_file, "Building Command Line") - global_tool_logs(self._build_version_command, config_file, "Building Version Command Line") + global_tool_logs(self._build_config_files, config_file, "Building Config Files", self.tool) + global_tool_logs(self._build_param_file, config_file, "Building Param File", self.tool) + global_tool_logs(self._build_command_line, config_file, "Building Command Line", self.tool) + global_tool_logs(self._build_version_command, config_file, "Building Version Command Line", self.tool) return ( self.command_line, self.version_command_line,