@@ -51,12 +51,12 @@ def sentry_init_error(*args, **kwargs):
51
51
52
52
exc_info = sys .exc_info ()
53
53
if exc_info and all (exc_info ):
54
- event , hint = event_from_exception (
54
+ sentry_event , hint = event_from_exception (
55
55
exc_info ,
56
56
client_options = client .options ,
57
57
mechanism = {"type" : "aws_lambda" , "handled" : False },
58
58
)
59
- hub .capture_event (event , hint = hint )
59
+ hub .capture_event (sentry_event , hint = hint )
60
60
61
61
return init_error (* args , ** kwargs )
62
62
@@ -65,12 +65,36 @@ def sentry_init_error(*args, **kwargs):
65
65
66
66
def _wrap_handler (handler ):
67
67
# type: (F) -> F
68
- def sentry_handler (event , context , * args , ** kwargs ):
68
+ def sentry_handler (aws_event , context , * args , ** kwargs ):
69
69
# type: (Any, Any, *Any, **Any) -> Any
70
+
71
+ # Per https://docs.aws.amazon.com/lambda/latest/dg/python-handler.html,
72
+ # `event` here is *likely* a dictionary, but also might be a number of
73
+ # other types (str, int, float, None).
74
+ #
75
+ # In some cases, it is a list (if the user is batch-invoking their
76
+ # function, for example), in which case we'll use the first entry as a
77
+ # representative from which to try pulling request data. (Presumably it
78
+ # will be the same for all events in the list, since they're all hitting
79
+ # the lambda in the same request.)
80
+
81
+ if isinstance (aws_event , list ):
82
+ request_data = aws_event [0 ]
83
+ batch_size = len (aws_event )
84
+ else :
85
+ request_data = aws_event
86
+ batch_size = 1
87
+
88
+ if not isinstance (request_data , dict ):
89
+ # If we're not dealing with a dictionary, we won't be able to get
90
+ # headers, path, http method, etc in any case, so it's fine that
91
+ # this is empty
92
+ request_data = {}
93
+
70
94
hub = Hub .current
71
95
integration = hub .get_integration (AwsLambdaIntegration )
72
96
if integration is None :
73
- return handler (event , context , * args , ** kwargs )
97
+ return handler (aws_event , context , * args , ** kwargs )
74
98
75
99
# If an integration is there, a client has to be there.
76
100
client = hub .client # type: Any
@@ -80,9 +104,14 @@ def sentry_handler(event, context, *args, **kwargs):
80
104
with capture_internal_exceptions ():
81
105
scope .clear_breadcrumbs ()
82
106
scope .add_event_processor (
83
- _make_request_event_processor (event , context , configured_time )
107
+ _make_request_event_processor (
108
+ request_data , context , configured_time
109
+ )
84
110
)
85
111
scope .set_tag ("aws_region" , context .invoked_function_arn .split (":" )[3 ])
112
+ if batch_size > 1 :
113
+ scope .set_tag ("batch_request" , True )
114
+ scope .set_tag ("batch_size" , batch_size )
86
115
87
116
timeout_thread = None
88
117
# Starting the Timeout thread only if the configured time is greater than Timeout warning
@@ -103,21 +132,21 @@ def sentry_handler(event, context, *args, **kwargs):
103
132
# Starting the thread to raise timeout warning exception
104
133
timeout_thread .start ()
105
134
106
- headers = event .get ("headers" , {})
135
+ headers = request_data .get ("headers" , {})
107
136
transaction = Transaction .continue_from_headers (
108
137
headers , op = "serverless.function" , name = context .function_name
109
138
)
110
139
with hub .start_transaction (transaction ):
111
140
try :
112
- return handler (event , context , * args , ** kwargs )
141
+ return handler (aws_event , context , * args , ** kwargs )
113
142
except Exception :
114
143
exc_info = sys .exc_info ()
115
- event , hint = event_from_exception (
144
+ sentry_event , hint = event_from_exception (
116
145
exc_info ,
117
146
client_options = client .options ,
118
147
mechanism = {"type" : "aws_lambda" , "handled" : False },
119
148
)
120
- hub .capture_event (event , hint = hint )
149
+ hub .capture_event (sentry_event , hint = hint )
121
150
reraise (* exc_info )
122
151
finally :
123
152
if timeout_thread :
@@ -255,12 +284,12 @@ def _make_request_event_processor(aws_event, aws_context, configured_timeout):
255
284
# type: (Any, Any, Any) -> EventProcessor
256
285
start_time = datetime .utcnow ()
257
286
258
- def event_processor (event , hint , start_time = start_time ):
287
+ def event_processor (sentry_event , hint , start_time = start_time ):
259
288
# type: (Event, Hint, datetime) -> Optional[Event]
260
289
remaining_time_in_milis = aws_context .get_remaining_time_in_millis ()
261
290
exec_duration = configured_timeout - remaining_time_in_milis
262
291
263
- extra = event .setdefault ("extra" , {})
292
+ extra = sentry_event .setdefault ("extra" , {})
264
293
extra ["lambda" ] = {
265
294
"function_name" : aws_context .function_name ,
266
295
"function_version" : aws_context .function_version ,
@@ -276,7 +305,7 @@ def event_processor(event, hint, start_time=start_time):
276
305
"log_stream" : aws_context .log_stream_name ,
277
306
}
278
307
279
- request = event .get ("request" , {})
308
+ request = sentry_event .get ("request" , {})
280
309
281
310
if "httpMethod" in aws_event :
282
311
request ["method" ] = aws_event ["httpMethod" ]
@@ -290,7 +319,7 @@ def event_processor(event, hint, start_time=start_time):
290
319
request ["headers" ] = _filter_headers (aws_event ["headers" ])
291
320
292
321
if _should_send_default_pii ():
293
- user_info = event .setdefault ("user" , {})
322
+ user_info = sentry_event .setdefault ("user" , {})
294
323
295
324
id = aws_event .get ("identity" , {}).get ("userArn" )
296
325
if id is not None :
@@ -308,31 +337,31 @@ def event_processor(event, hint, start_time=start_time):
308
337
# event. Meaning every body is unstructured to us.
309
338
request ["data" ] = AnnotatedValue ("" , {"rem" : [["!raw" , "x" , 0 , 0 ]]})
310
339
311
- event ["request" ] = request
340
+ sentry_event ["request" ] = request
312
341
313
- return event
342
+ return sentry_event
314
343
315
344
return event_processor
316
345
317
346
318
- def _get_url (event , context ):
347
+ def _get_url (aws_event , aws_context ):
319
348
# type: (Any, Any) -> str
320
- path = event .get ("path" , None )
321
- headers = event .get ("headers" , {})
349
+ path = aws_event .get ("path" , None )
350
+ headers = aws_event .get ("headers" , {})
322
351
host = headers .get ("Host" , None )
323
352
proto = headers .get ("X-Forwarded-Proto" , None )
324
353
if proto and host and path :
325
354
return "{}://{}{}" .format (proto , host , path )
326
- return "awslambda:///{}" .format (context .function_name )
355
+ return "awslambda:///{}" .format (aws_context .function_name )
327
356
328
357
329
- def _get_cloudwatch_logs_url (context , start_time ):
358
+ def _get_cloudwatch_logs_url (aws_context , start_time ):
330
359
# type: (Any, datetime) -> str
331
360
"""
332
361
Generates a CloudWatchLogs console URL based on the context object
333
362
334
363
Arguments:
335
- context {Any} -- context from lambda handler
364
+ aws_context {Any} -- context from lambda handler
336
365
337
366
Returns:
338
367
str -- AWS Console URL to logs.
@@ -345,8 +374,8 @@ def _get_cloudwatch_logs_url(context, start_time):
345
374
";start={start_time};end={end_time}"
346
375
).format (
347
376
region = environ .get ("AWS_REGION" ),
348
- log_group = context .log_group_name ,
349
- log_stream = context .log_stream_name ,
377
+ log_group = aws_context .log_group_name ,
378
+ log_stream = aws_context .log_stream_name ,
350
379
start_time = (start_time - timedelta (seconds = 1 )).strftime (formatstring ),
351
380
end_time = (datetime .utcnow () + timedelta (seconds = 2 )).strftime (formatstring ),
352
381
)
0 commit comments