100 lines
4.2 KiB
Python
100 lines
4.2 KiB
Python
from SpiffWorkflow import WorkflowException
|
|
from SpiffWorkflow.exceptions import WorkflowTaskExecException
|
|
from flask import g
|
|
from werkzeug.exceptions import InternalServerError
|
|
|
|
from crc import ma, app
|
|
|
|
import sentry_sdk
|
|
|
|
|
|
class ApiError(Exception):
|
|
def __init__(self, code, message, status_code=400,
|
|
file_name="", task_id="", task_name="", tag="", task_data=None, error_type="", line_number=0, offset=0):
|
|
if task_data is None:
|
|
task_data = {}
|
|
self.status_code = status_code
|
|
self.code = code # a short consistent string describing the error.
|
|
self.message = message # A detailed message that provides more information.
|
|
self.task_id = task_id or "" # OPTIONAL: The id of the task in the BPMN Diagram.
|
|
self.task_name = task_name or "" # OPTIONAL: The name of the task in the BPMN Diagram.
|
|
self.file_name = file_name or "" # OPTIONAL: The file that caused the error.
|
|
self.tag = tag or "" # OPTIONAL: The XML Tag that caused the issue.
|
|
self.task_data = task_data or "" # OPTIONAL: A snapshot of data connected to the task when error occurred.
|
|
self.line_number = line_number
|
|
self.offset = offset
|
|
self.error_type = error_type
|
|
if hasattr(g, 'user'):
|
|
user = g.user.uid
|
|
else:
|
|
user = 'Unknown'
|
|
self.task_user = user
|
|
# This is for sentry logging into Slack
|
|
sentry_sdk.set_context("User", {'user': user})
|
|
Exception.__init__(self, self.message)
|
|
|
|
@classmethod
|
|
def from_task(cls, code, message, task, status_code=400, line_number=0, offset=0, error_type="", error_line=""):
|
|
"""Constructs an API Error with details pulled from the current task."""
|
|
instance = cls(code, message, status_code=status_code)
|
|
instance.task_id = task.task_spec.name or ""
|
|
instance.task_name = task.task_spec.description or ""
|
|
instance.file_name = task.workflow.spec.file or ""
|
|
instance.line_number = line_number
|
|
instance.offset = offset
|
|
instance.error_type = error_type
|
|
instance.error_line = error_line
|
|
|
|
# Fixme: spiffworkflow is doing something weird where task ends up referenced in the data in some cases.
|
|
if "task" in task.data:
|
|
task.data.pop("task")
|
|
|
|
instance.task_data = task.data
|
|
app.logger.error(message, exc_info=True)
|
|
return instance
|
|
|
|
@classmethod
|
|
def from_task_spec(cls, code, message, task_spec, status_code=400):
|
|
"""Constructs an API Error with details pulled from the current task."""
|
|
instance = cls(code, message, status_code=status_code)
|
|
instance.task_id = task_spec.name or ""
|
|
instance.task_name = task_spec.description or ""
|
|
if task_spec._wf_spec:
|
|
instance.file_name = task_spec._wf_spec.file
|
|
app.logger.error(message, exc_info=True)
|
|
return instance
|
|
|
|
@classmethod
|
|
def from_workflow_exception(cls, code, message, exp: WorkflowException):
|
|
"""We catch a lot of workflow exception errors,
|
|
so consolidating the code, and doing the best things
|
|
we can with the data we have."""
|
|
if isinstance(exp, WorkflowTaskExecException):
|
|
return ApiError.from_task(code, message, exp.task, line_number=exp.line_number,
|
|
offset=exp.offset,
|
|
error_type=exp.exception.__class__.__name__,
|
|
error_line=exp.error_line)
|
|
|
|
else:
|
|
return ApiError.from_task_spec(code, message, exp.sender)
|
|
|
|
|
|
class ApiErrorSchema(ma.Schema):
|
|
class Meta:
|
|
fields = ("code", "message", "workflow_name", "file_name", "task_name", "task_id",
|
|
"task_data", "task_user", "hint", "line_number", "offset", "error_type", "error_line")
|
|
|
|
|
|
@app.errorhandler(ApiError)
|
|
def handle_invalid_usage(error):
|
|
response = ApiErrorSchema().dump(error)
|
|
return response, error.status_code
|
|
|
|
|
|
@app.errorhandler(InternalServerError)
|
|
def handle_internal_server_error(e):
|
|
original = getattr(e, "original_exception", None)
|
|
api_error = ApiError(code='Internal Server Error (500)', message=str(original))
|
|
response = ApiErrorSchema().dump(api_error)
|
|
return response, 500
|