11from datetime import datetime , timedelta
22from os import environ
33import sys
4+ import json
45
56from sentry_sdk .hub import Hub , _should_send_default_pii
67from sentry_sdk ._compat import reraise
910 capture_internal_exceptions ,
1011 event_from_exception ,
1112 logger ,
13+ TimeoutThread ,
1214)
1315from sentry_sdk .integrations import Integration
1416from sentry_sdk .integrations ._wsgi_common import _filter_headers
2527
2628 F = TypeVar ("F" , bound = Callable [..., Any ])
2729
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+
2869
2970def _wrap_handler (handler ):
3071 # type: (F) -> F
@@ -37,12 +78,31 @@ def sentry_handler(event, context, *args, **kwargs):
3778
3879 # If an integration is there, a client has to be there.
3980 client = hub .client # type: Any
81+ configured_time = context .get_remaining_time_in_millis ()
4082
4183 with hub .push_scope () as scope :
4284 with capture_internal_exceptions ():
4385 scope .clear_breadcrumbs ()
4486 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 ()
46106
47107 try :
48108 return handler (event , context , * args , ** kwargs )
@@ -73,6 +133,10 @@ def _drain_queue():
73133class AwsLambdaIntegration (Integration ):
74134 identifier = "aws_lambda"
75135
136+ def __init__ (self , timeout_warning = False ):
137+ # type: (bool) -> None
138+ self .timeout_warning = timeout_warning
139+
76140 @staticmethod
77141 def setup_once ():
78142 # type: () -> None
@@ -126,6 +190,10 @@ def sentry_to_json(*args, **kwargs):
126190
127191 lambda_bootstrap .to_json = sentry_to_json
128192 else :
193+ lambda_bootstrap .LambdaRuntimeClient .post_init_error = _wrap_init_error (
194+ lambda_bootstrap .LambdaRuntimeClient .post_init_error
195+ )
196+
129197 old_handle_event_request = lambda_bootstrap .handle_event_request
130198
131199 def sentry_handle_event_request ( # type: ignore
@@ -158,19 +226,23 @@ def inner(*args, **kwargs):
158226 )
159227
160228
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
163231 start_time = datetime .now ()
164232
165233 def event_processor (event , hint , start_time = start_time ):
166234 # 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+
167238 extra = event .setdefault ("extra" , {})
168239 extra ["lambda" ] = {
169240 "function_name" : aws_context .function_name ,
170241 "function_version" : aws_context .function_version ,
171242 "invoked_function_arn" : aws_context .invoked_function_arn ,
172- "remaining_time_in_millis" : aws_context .get_remaining_time_in_millis (),
173243 "aws_request_id" : aws_context .aws_request_id ,
244+ "execution_duration_in_millis" : exec_duration ,
245+ "remaining_time_in_millis" : remaining_time_in_milis ,
174246 }
175247
176248 extra ["cloudwatch logs" ] = {
0 commit comments