Skip to content

Commit 1ad7163

Browse files
fix(aws): Inject scopes in TimeoutThread exception with AWS lambda (#4914)
Restore ServerlessTimeoutWarning isolation and current scope handling, so the scopes are active in an AWS lambda function and breadcrumbs and tags are applied on timeout. The behavior is restored to that before 2d392af. More information about how I found the bug is described in #4912. Closes #4894.
1 parent 2e259ae commit 1ad7163

File tree

5 files changed

+91
-2
lines changed

5 files changed

+91
-2
lines changed

sentry_sdk/integrations/aws_lambda.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ def sentry_handler(aws_event, aws_context, *args, **kwargs):
138138
timeout_thread = TimeoutThread(
139139
waiting_time,
140140
configured_time / MILLIS_TO_SECONDS,
141+
isolation_scope=scope,
142+
current_scope=sentry_sdk.get_current_scope(),
141143
)
142144

143145
# Starting the thread to raise timeout warning exception

sentry_sdk/utils.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1484,17 +1484,37 @@ class TimeoutThread(threading.Thread):
14841484
waiting_time and raises a custom ServerlessTimeout exception.
14851485
"""
14861486

1487-
def __init__(self, waiting_time, configured_timeout):
1488-
# type: (float, int) -> None
1487+
def __init__(
1488+
self, waiting_time, configured_timeout, isolation_scope=None, current_scope=None
1489+
):
1490+
# type: (float, int, Optional[sentry_sdk.Scope], Optional[sentry_sdk.Scope]) -> None
14891491
threading.Thread.__init__(self)
14901492
self.waiting_time = waiting_time
14911493
self.configured_timeout = configured_timeout
1494+
1495+
self.isolation_scope = isolation_scope
1496+
self.current_scope = current_scope
1497+
14921498
self._stop_event = threading.Event()
14931499

14941500
def stop(self):
14951501
# type: () -> None
14961502
self._stop_event.set()
14971503

1504+
def _capture_exception(self):
1505+
# type: () -> ExcInfo
1506+
exc_info = sys.exc_info()
1507+
1508+
client = sentry_sdk.get_client()
1509+
event, hint = event_from_exception(
1510+
exc_info,
1511+
client_options=client.options,
1512+
mechanism={"type": "threading", "handled": False},
1513+
)
1514+
sentry_sdk.capture_event(event, hint=hint)
1515+
1516+
return exc_info
1517+
14981518
def run(self):
14991519
# type: () -> None
15001520

@@ -1510,6 +1530,18 @@ def run(self):
15101530
integer_configured_timeout = integer_configured_timeout + 1
15111531

15121532
# Raising Exception after timeout duration is reached
1533+
if self.isolation_scope is not None and self.current_scope is not None:
1534+
with sentry_sdk.scope.use_isolation_scope(self.isolation_scope):
1535+
with sentry_sdk.scope.use_scope(self.current_scope):
1536+
try:
1537+
raise ServerlessTimeoutWarning(
1538+
"WARNING : Function is expected to get timed out. Configured timeout duration = {} seconds.".format(
1539+
integer_configured_timeout
1540+
)
1541+
)
1542+
except Exception:
1543+
reraise(*self._capture_exception())
1544+
15131545
raise ServerlessTimeoutWarning(
15141546
"WARNING : Function is expected to get timed out. Configured timeout duration = {} seconds.".format(
15151547
integer_configured_timeout
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Need to add some ignore rules in this directory, because the unit tests will add the Sentry SDK and its dependencies
2+
# into this directory to create a Lambda function package that contains everything needed to instrument a Lambda function using Sentry.
3+
4+
# Ignore everything
5+
*
6+
7+
# But not index.py
8+
!index.py
9+
10+
# And not .gitignore itself
11+
!.gitignore
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import os
2+
import time
3+
4+
import sentry_sdk
5+
from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration
6+
7+
sentry_sdk.init(
8+
dsn=os.environ.get("SENTRY_DSN"),
9+
traces_sample_rate=1.0,
10+
integrations=[AwsLambdaIntegration(timeout_warning=True)],
11+
)
12+
13+
14+
def handler(event, context):
15+
sentry_sdk.set_tag("custom_tag", "custom_value")
16+
time.sleep(15)
17+
return {
18+
"event": event,
19+
}

tests/integrations/aws_lambda/test_aws_lambda.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,31 @@ def test_timeout_error(lambda_client, test_environment):
223223
assert exception["mechanism"]["type"] == "threading"
224224

225225

226+
def test_timeout_error_scope_modified(lambda_client, test_environment):
227+
lambda_client.invoke(
228+
FunctionName="TimeoutErrorScopeModified",
229+
Payload=json.dumps({}),
230+
)
231+
envelopes = test_environment["server"].envelopes
232+
233+
(error_event,) = envelopes
234+
235+
assert error_event["level"] == "error"
236+
assert (
237+
error_event["extra"]["lambda"]["function_name"] == "TimeoutErrorScopeModified"
238+
)
239+
240+
(exception,) = error_event["exception"]["values"]
241+
assert not exception["mechanism"]["handled"]
242+
assert exception["type"] == "ServerlessTimeoutWarning"
243+
assert exception["value"].startswith(
244+
"WARNING : Function is expected to get timed out. Configured timeout duration ="
245+
)
246+
assert exception["mechanism"]["type"] == "threading"
247+
248+
assert error_event["tags"]["custom_tag"] == "custom_value"
249+
250+
226251
@pytest.mark.parametrize(
227252
"aws_event, has_request_data, batch_size",
228253
[

0 commit comments

Comments
 (0)