Skip to content

Commit 59499cd

Browse files
feat: OPTIC-1749: Only send 5XX API Exceptions to Sentry (#7217)
Co-authored-by: robot-ci-heartex <[email protected]>
1 parent 91a8851 commit 59499cd

File tree

6 files changed

+28
-37
lines changed

6 files changed

+28
-37
lines changed

label_studio/core/label_config.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,8 @@
1414
import xmljson
1515
from django.conf import settings
1616
from label_studio_sdk._extensions.label_studio_tools.core import label_config
17+
from rest_framework.exceptions import ValidationError
1718

18-
from label_studio.core.utils.exceptions import (
19-
LabelStudioValidationErrorSentryIgnored,
20-
)
2119
from label_studio.core.utils.io import find_file
2220

2321
logger = logging.getLogger(__name__)
@@ -110,7 +108,7 @@ def validate_label_config(config_string: Union[str, None]) -> None:
110108
config, cleaned_config_string = parse_config_to_json(config_string)
111109
jsonschema.validate(config, _LABEL_CONFIG_SCHEMA_DATA)
112110
except (etree.ParseError, ValueError) as exc:
113-
raise LabelStudioValidationErrorSentryIgnored(str(exc))
111+
raise ValidationError(str(exc))
114112
except jsonschema.exceptions.ValidationError as exc:
115113
# jsonschema4 validation error now includes all errors from "anyOf" subschemas
116114
# check https://python-jsonschema.readthedocs.io/en/latest/errors/#jsonschema.exceptions.ValidationError.context
@@ -119,20 +117,20 @@ def validate_label_config(config_string: Union[str, None]) -> None:
119117
error_message = 'Validation failed on {}: {}'.format(
120118
'/'.join(map(str, exc.path)), error_message.replace('@', '')
121119
)
122-
raise LabelStudioValidationErrorSentryIgnored(error_message)
120+
raise ValidationError(error_message)
123121

124122
# unique names in config # FIXME: 'name =' (with spaces) won't work
125123
all_names = re.findall(r'name="([^"]*)"', cleaned_config_string)
126124
if len(set(all_names)) != len(all_names):
127-
raise LabelStudioValidationErrorSentryIgnored('Label config contains non-unique names')
125+
raise ValidationError('Label config contains non-unique names')
128126

129127
# toName points to existent name
130128
names = set(all_names)
131129
toNames = re.findall(r'toName="([^"]*)"', cleaned_config_string)
132130
for toName_ in toNames:
133131
for toName in toName_.split(','):
134132
if toName not in names:
135-
raise LabelStudioValidationErrorSentryIgnored(f'toName="{toName}" not found in names: {sorted(names)}')
133+
raise ValidationError(f'toName="{toName}" not found in names: {sorted(names)}')
136134

137135

138136
def extract_data_types(label_config):

label_studio/core/settings/base.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -388,16 +388,14 @@
388388
FRONTEND_SENTRY_DSN = get_env('FRONTEND_SENTRY_DSN', None)
389389
FRONTEND_SENTRY_RATE = get_env('FRONTEND_SENTRY_RATE', 0.01)
390390
FRONTEND_SENTRY_ENVIRONMENT = get_env('FRONTEND_SENTRY_ENVIRONMENT', 'stage.opensource')
391+
392+
# Exceptions that should not be logged to Sentry and aren't children of drf's APIException class
391393
SENTRY_IGNORED_EXCEPTIONS = [
392394
'Http404',
393-
'NotAuthenticated',
394-
'AuthenticationFailed',
395-
'NotFound',
396395
'XMLSyntaxError',
397396
'FileUpload.DoesNotExist',
398397
'Forbidden',
399398
'KeyboardInterrupt',
400-
'PermissionDenied',
401399
]
402400

403401
ROOT_URLCONF = 'core.urls'

label_studio/core/utils/common.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
from packaging.version import parse as parse_version
5353
from pyboxen import boxen
5454
from rest_framework import status
55-
from rest_framework.exceptions import ErrorDetail
55+
from rest_framework.exceptions import APIException, ErrorDetail
5656
from rest_framework.views import Response, exception_handler
5757

5858
import label_studio
@@ -89,7 +89,17 @@ def custom_exception_handler(exc, context):
8989
:return: response with error desc
9090
"""
9191
exception_id = uuid.uuid4()
92-
logger.error('{} {}'.format(exception_id, exc), exc_info=True)
92+
93+
sentry_skip = False
94+
if isinstance(exc, APIException) and exc.status_code < 500:
95+
# Skipping Sentry for non-500 unhandled exceptions
96+
sentry_skip = True
97+
98+
logger.error(
99+
'{} {}'.format(exception_id, exc),
100+
exc_info=True,
101+
extra={'sentry_skip': sentry_skip, 'exception_id': exception_id},
102+
)
93103

94104
exc = _override_exceptions(exc)
95105

@@ -134,7 +144,9 @@ def custom_exception_handler(exc, context):
134144
if not settings.DEBUG_MODAL_EXCEPTIONS:
135145
exc_tb = None
136146
response_data['exc_info'] = exc_tb
147+
# Thrown by sdk when label config is invalid
137148
if isinstance(exc, LabelStudioXMLSyntaxErrorSentryIgnored):
149+
response_data['status_code'] = status.HTTP_400_BAD_REQUEST
138150
response = Response(status=status.HTTP_400_BAD_REQUEST, data=response_data)
139151
else:
140152
response = Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR, data=response_data)

label_studio/core/utils/exceptions.py

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""This file and its contents are licensed under the Apache License 2.0. Please see the included NOTICE for copyright information and LICENSE for a copy of the license.
22
"""
33
from rest_framework import status
4-
from rest_framework.exceptions import APIException, ValidationError
4+
from rest_framework.exceptions import APIException
55

66

77
class LabelStudioError(Exception):
@@ -27,22 +27,6 @@ class ProjectExistException(LabelStudioAPIException):
2727
default_detail = 'Project with the same title already exists'
2828

2929

30-
class LabelStudioErrorSentryIgnored(Exception):
31-
pass
32-
33-
34-
class LabelStudioAPIExceptionSentryIgnored(LabelStudioAPIException):
35-
pass
36-
37-
38-
class LabelStudioValidationErrorSentryIgnored(ValidationError):
39-
pass
40-
41-
42-
class LabelStudioXMLSyntaxErrorSentryIgnored(Exception):
43-
pass
44-
45-
4630
class InvalidUploadUrlError(LabelStudioAPIException):
4731
default_detail = (
4832
'The provided URL was not valid. URLs must begin with http:// or https://, and cannot be local IPs.'

label_studio/data_import/api.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
from core.permissions import ViewClassPermission, all_permissions
1515
from core.redis import start_job_async_or_sync
1616
from core.utils.common import retry_database_locked, timeit
17-
from core.utils.exceptions import LabelStudioValidationErrorSentryIgnored
1817
from core.utils.params import bool_from_request, list_of_strings_from_request
1918
from csp.decorators import csp
2019
from django.conf import settings
@@ -413,7 +412,7 @@ def create(self, request, *args, **kwargs):
413412
predictions = []
414413
for item in self.request.data:
415414
if item.get('task') not in tasks_ids:
416-
raise LabelStudioValidationErrorSentryIgnored(
415+
raise ValidationError(
417416
f'{item} contains invalid "task" field: corresponding task ID couldn\'t be retrieved '
418417
f'from project {project} tasks'
419418
)

label_studio/projects/models.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
merge_labels_counters,
2727
)
2828
from core.utils.db import fast_first
29-
from core.utils.exceptions import LabelStudioValidationErrorSentryIgnored
3029
from django.conf import settings
3130
from django.core.validators import MaxLengthValidator, MinLengthValidator
3231
from django.db import models, transaction
@@ -46,6 +45,7 @@
4645
)
4746
from projects.functions.utils import make_queryset_from_iterable
4847
from projects.signals import ProjectSignals
48+
from rest_framework.exceptions import ValidationError
4949
from tasks.models import (
5050
Annotation,
5151
AnnotationDraft,
@@ -546,7 +546,7 @@ def validate_config(self, config_string, strict=False):
546546
fields_from_data.discard(settings.DATA_UNDEFINED_NAME)
547547
if fields_from_data and not fields_from_config.issubset(fields_from_data):
548548
different_fields = list(fields_from_config.difference(fields_from_data))
549-
raise LabelStudioValidationErrorSentryIgnored(
549+
raise ValidationError(
550550
f'These fields are not present in the data: {",".join(different_fields)}'
551551
)"""
552552

@@ -585,7 +585,7 @@ def validate_config(self, config_string, strict=False):
585585
)
586586
if len(diff_str) > 0:
587587
diff_str = '\n'.join(diff_str)
588-
raise LabelStudioValidationErrorSentryIgnored(
588+
raise ValidationError(
589589
f'Created annotations are incompatible with provided labeling schema, we found:\n{diff_str}'
590590
)
591591

@@ -611,7 +611,7 @@ def display_count(count: int, type: str) -> Optional[str]:
611611
)
612612
and not check_control_in_config_by_regex(config_string, control_tag_from_data)
613613
):
614-
raise LabelStudioValidationErrorSentryIgnored(
614+
raise ValidationError(
615615
f'There are {sum(labels_from_data.values(), 0)} annotation(s) created with tag '
616616
f'"{control_tag_from_data}", you can\'t remove it'
617617
)
@@ -651,7 +651,7 @@ def display_count(count: int, type: str) -> Optional[str]:
651651
)
652652
):
653653
# raise error if labels not dynamic and not in regex rules
654-
raise LabelStudioValidationErrorSentryIgnored(
654+
raise ValidationError(
655655
f'These labels still exist in annotations or drafts:\n{diff_str}'
656656
f'Please add labels to tag with name="{str(control_tag_from_data)}".'
657657
)

0 commit comments

Comments
 (0)