1
1
from datetime import datetime , timedelta
2
2
from os import environ
3
3
import sys
4
+ import json
4
5
5
6
from sentry_sdk .hub import Hub , _should_send_default_pii
6
7
from sentry_sdk ._compat import reraise
9
10
capture_internal_exceptions ,
10
11
event_from_exception ,
11
12
logger ,
13
+ TimeoutThread ,
12
14
)
13
15
from sentry_sdk .integrations import Integration
14
16
from sentry_sdk .integrations ._wsgi_common import _filter_headers
25
27
26
28
F = TypeVar ("F" , bound = Callable [..., Any ])
27
29
30
+ # Constants
31
+ TIMEOUT_WARNING_BUFFER = 1500 # Buffer time required to send timeout warning to Sentry
32
+ MILLIS_TO_SECONDS = 1000.0
33
+
34
+
35
+ def _wrap_init_error (init_error ):
36
+ # type: (F) -> F
37
+ def sentry_init_error (* args , ** kwargs ):
38
+ # type: (*Any, **Any) -> Any
39
+
40
+ hub = Hub .current
41
+ integration = hub .get_integration (AwsLambdaIntegration )
42
+ if integration is None :
43
+ return init_error (* args , ** kwargs )
44
+
45
+ # Fetch Initialization error details from arguments
46
+ error = json .loads (args [1 ])
47
+
48
+ # If an integration is there, a client has to be there.
49
+ client = hub .client # type: Any
50
+
51
+ with hub .push_scope () as scope :
52
+ with capture_internal_exceptions ():
53
+ scope .clear_breadcrumbs ()
54
+ # Checking if there is any error/exception which is raised in the runtime
55
+ # environment from arguments and, re-raising it to capture it as an event.
56
+ if error .get ("errorType" ):
57
+ exc_info = sys .exc_info ()
58
+ event , hint = event_from_exception (
59
+ exc_info ,
60
+ client_options = client .options ,
61
+ mechanism = {"type" : "aws_lambda" , "handled" : False },
62
+ )
63
+ hub .capture_event (event , hint = hint )
64
+
65
+ return init_error (* args , ** kwargs )
66
+
67
+ return sentry_init_error # type: ignore
68
+
28
69
29
70
def _wrap_handler (handler ):
30
71
# type: (F) -> F
@@ -37,12 +78,31 @@ def sentry_handler(event, context, *args, **kwargs):
37
78
38
79
# If an integration is there, a client has to be there.
39
80
client = hub .client # type: Any
81
+ configured_time = context .get_remaining_time_in_millis ()
40
82
41
83
with hub .push_scope () as scope :
42
84
with capture_internal_exceptions ():
43
85
scope .clear_breadcrumbs ()
44
86
scope .transaction = context .function_name
45
- scope .add_event_processor (_make_request_event_processor (event , context ))
87
+ scope .add_event_processor (
88
+ _make_request_event_processor (event , context , configured_time )
89
+ )
90
+ # Starting the Timeout thread only if the configured time is greater than Timeout warning
91
+ # buffer and timeout_warning parameter is set True.
92
+ if (
93
+ integration .timeout_warning
94
+ and configured_time > TIMEOUT_WARNING_BUFFER
95
+ ):
96
+ waiting_time = (
97
+ configured_time - TIMEOUT_WARNING_BUFFER
98
+ ) / MILLIS_TO_SECONDS
99
+
100
+ timeout_thread = TimeoutThread (
101
+ waiting_time , configured_time / MILLIS_TO_SECONDS
102
+ )
103
+
104
+ # Starting the thread to raise timeout warning exception
105
+ timeout_thread .start ()
46
106
47
107
try :
48
108
return handler (event , context , * args , ** kwargs )
@@ -73,6 +133,10 @@ def _drain_queue():
73
133
class AwsLambdaIntegration (Integration ):
74
134
identifier = "aws_lambda"
75
135
136
+ def __init__ (self , timeout_warning = False ):
137
+ # type: (bool) -> None
138
+ self .timeout_warning = timeout_warning
139
+
76
140
@staticmethod
77
141
def setup_once ():
78
142
# type: () -> None
@@ -126,6 +190,10 @@ def sentry_to_json(*args, **kwargs):
126
190
127
191
lambda_bootstrap .to_json = sentry_to_json
128
192
else :
193
+ lambda_bootstrap .LambdaRuntimeClient .post_init_error = _wrap_init_error (
194
+ lambda_bootstrap .LambdaRuntimeClient .post_init_error
195
+ )
196
+
129
197
old_handle_event_request = lambda_bootstrap .handle_event_request
130
198
131
199
def sentry_handle_event_request ( # type: ignore
@@ -158,19 +226,23 @@ def inner(*args, **kwargs):
158
226
)
159
227
160
228
161
- def _make_request_event_processor (aws_event , aws_context ):
162
- # type: (Any, Any) -> EventProcessor
229
+ def _make_request_event_processor (aws_event , aws_context , configured_timeout ):
230
+ # type: (Any, Any, Any ) -> EventProcessor
163
231
start_time = datetime .now ()
164
232
165
233
def event_processor (event , hint , start_time = start_time ):
166
234
# type: (Event, Hint, datetime) -> Optional[Event]
235
+ remaining_time_in_milis = aws_context .get_remaining_time_in_millis ()
236
+ exec_duration = configured_timeout - remaining_time_in_milis
237
+
167
238
extra = event .setdefault ("extra" , {})
168
239
extra ["lambda" ] = {
169
240
"function_name" : aws_context .function_name ,
170
241
"function_version" : aws_context .function_version ,
171
242
"invoked_function_arn" : aws_context .invoked_function_arn ,
172
- "remaining_time_in_millis" : aws_context .get_remaining_time_in_millis (),
173
243
"aws_request_id" : aws_context .aws_request_id ,
244
+ "execution_duration_in_millis" : exec_duration ,
245
+ "remaining_time_in_millis" : remaining_time_in_milis ,
174
246
}
175
247
176
248
extra ["cloudwatch logs" ] = {
0 commit comments