39
39
import elasticapm
40
40
from elasticapm .base import Client , get_client
41
41
from elasticapm .conf import constants
42
- from elasticapm .utils import compat , encoding , get_name_from_func
42
+ from elasticapm .utils import compat , encoding , get_name_from_func , nested_key
43
43
from elasticapm .utils .disttracing import TraceParent
44
44
from elasticapm .utils .logging import get_logger
45
45
@@ -63,9 +63,6 @@ class capture_serverless(object):
63
63
@capture_serverless()
64
64
def handler(event, context):
65
65
return {"statusCode": r.status_code, "body": "Success!"}
66
-
67
- Note: This is an experimental feature, and we may introduce breaking
68
- changes in the future.
69
66
"""
70
67
71
68
def __init__ (self , name = None , ** kwargs ):
@@ -79,8 +76,6 @@ def __init__(self, name=None, **kwargs):
79
76
kwargs ["central_config" ] = False
80
77
kwargs ["cloud_provider" ] = "none"
81
78
kwargs ["framework_name" ] = "AWS Lambda"
82
- # TODO this can probably be removed once the extension proxies the serverinfo endpoint
83
- kwargs ["server_version" ] = (8 , 0 , 0 )
84
79
if "service_name" not in kwargs :
85
80
kwargs ["service_name" ] = os .environ ["AWS_LAMBDA_FUNCTION_NAME" ]
86
81
@@ -123,10 +118,13 @@ def __enter__(self):
123
118
transaction_type = "request"
124
119
transaction_name = os .environ .get ("AWS_LAMBDA_FUNCTION_NAME" , self .name )
125
120
126
- if "httpMethod" in self .event : # API Gateway
121
+ self .httpmethod = nested_key (self .event , "requestContext" , "httpMethod" ) or nested_key (
122
+ self .event , "requestContext" , "http" , "method"
123
+ )
124
+ if self .httpmethod : # API Gateway
127
125
self .source = "api"
128
126
if os .environ .get ("AWS_LAMBDA_FUNCTION_NAME" ):
129
- transaction_name = "{} {}" .format (self .event [ "httpMethod" ] , os .environ ["AWS_LAMBDA_FUNCTION_NAME" ])
127
+ transaction_name = "{} {}" .format (self .httpmethod , os .environ ["AWS_LAMBDA_FUNCTION_NAME" ])
130
128
else :
131
129
transaction_name = self .name
132
130
elif "Records" in self .event and len (self .event ["Records" ]) == 1 :
@@ -160,9 +158,6 @@ def __exit__(self, exc_type, exc_val, exc_tb):
160
158
"""
161
159
Transaction teardown
162
160
"""
163
- if exc_val :
164
- self .client .capture_exception (exc_info = (exc_type , exc_val , exc_tb ), handled = False )
165
-
166
161
if self .response and isinstance (self .response , dict ):
167
162
elasticapm .set_context (
168
163
lambda : get_data_from_response (self .response , capture_headers = self .client .config .capture_headers ),
@@ -180,6 +175,16 @@ def __exit__(self, exc_type, exc_val, exc_tb):
180
175
result = "HTTP {}xx" .format (int (status_code ) // 100 )
181
176
elasticapm .set_transaction_result (result , override = False )
182
177
178
+ if exc_val :
179
+ self .client .capture_exception (exc_info = (exc_type , exc_val , exc_tb ), handled = False )
180
+ if self .source == "api" :
181
+ elasticapm .set_transaction_result ("HTTP 5xx" , override = False )
182
+ elasticapm .set_transaction_outcome (http_status_code = 500 , override = False )
183
+ elasticapm .set_context ({"status_code" : 500 }, "response" )
184
+ else :
185
+ elasticapm .set_transaction_result ("failure" , override = False )
186
+ elasticapm .set_transaction_outcome (outcome = "failure" , override = False )
187
+
183
188
self .client .end_transaction ()
184
189
185
190
try :
@@ -206,15 +211,19 @@ def set_metadata_and_context(self, coldstart):
206
211
if self .source == "api" :
207
212
faas ["trigger" ]["type" ] = "http"
208
213
faas ["trigger" ]["request_id" ] = self .event ["requestContext" ]["requestId" ]
214
+ path = (
215
+ self .event ["requestContext" ].get ("resourcePath" )
216
+ or self .event ["requestContext" ]["http" ]["path" ].split (self .event ["requestContext" ]["stage" ])[- 1 ]
217
+ )
209
218
service_context ["origin" ] = {
210
219
"name" : "{} {}/{}" .format (
211
- self .event [ "requestContext" ][ "httpMethod" ] ,
212
- self . event [ "requestContext" ][ "resourcePath" ] ,
220
+ self .httpmethod ,
221
+ path ,
213
222
self .event ["requestContext" ]["stage" ],
214
223
)
215
224
}
216
225
service_context ["origin" ]["id" ] = self .event ["requestContext" ]["apiId" ]
217
- service_context ["origin" ]["version" ] = "2.0" if self .event [ "headers" ][ "Via" ]. startswith ( "2.0" ) else "1.0"
226
+ service_context ["origin" ]["version" ] = self .event . get ( "version" , "1.0" )
218
227
cloud_context ["origin" ] = {}
219
228
cloud_context ["origin" ]["service" ] = {"name" : "api gateway" }
220
229
cloud_context ["origin" ]["account" ] = {"id" : self .event ["requestContext" ]["accountId" ]}
@@ -236,7 +245,7 @@ def set_metadata_and_context(self, coldstart):
236
245
message_context ["age" ] = int ((time .time () * 1000 ) - int (record ["attributes" ]["SentTimestamp" ]))
237
246
if self .client .config .capture_body in ("transactions" , "all" ) and "body" in record :
238
247
message_context ["body" ] = record ["body" ]
239
- if self .client .config .capture_headers and record [ "messageAttributes" ] :
248
+ if self .client .config .capture_headers and record . get ( "messageAttributes" ) :
240
249
message_context ["headers" ] = record ["messageAttributes" ]
241
250
elif self .source == "sns" :
242
251
record = self .event ["Records" ][0 ]
@@ -262,7 +271,7 @@ def set_metadata_and_context(self, coldstart):
262
271
)
263
272
if self .client .config .capture_body in ("transactions" , "all" ) and "Message" in record ["Sns" ]:
264
273
message_context ["body" ] = record ["Sns" ]["Message" ]
265
- if self .client .config .capture_headers and record ["Sns" ][ "MessageAttributes" ] :
274
+ if self .client .config .capture_headers and record ["Sns" ]. get ( "MessageAttributes" ) :
266
275
message_context ["headers" ] = record ["Sns" ]["MessageAttributes" ]
267
276
elif self .source == "s3" :
268
277
record = self .event ["Records" ][0 ]
@@ -277,8 +286,6 @@ def set_metadata_and_context(self, coldstart):
277
286
cloud_context ["origin" ]["region" ] = record ["awsRegion" ]
278
287
cloud_context ["origin" ]["provider" ] = "aws"
279
288
280
- metadata ["faas" ] = faas
281
-
282
289
metadata ["service" ] = {}
283
290
metadata ["service" ]["name" ] = os .environ .get ("AWS_LAMBDA_FUNCTION_NAME" )
284
291
metadata ["service" ]["framework" ] = {"name" : "AWS Lambda" }
@@ -295,7 +302,7 @@ def set_metadata_and_context(self, coldstart):
295
302
# This is the one piece of metadata that requires deep merging. We add it manually
296
303
# here to avoid having to deep merge in _transport.add_metadata()
297
304
if self .client ._transport ._metadata :
298
- node_name = self .client ._transport ._metadata . get ( "service" , {}). get ( "node" , {}). get ( "name" )
305
+ node_name = nested_key ( self .client ._transport ._metadata , "service" , "node" , "name" )
299
306
if node_name :
300
307
metadata ["service" ]["node" ]["name" ] = node_name
301
308
@@ -307,6 +314,8 @@ def set_metadata_and_context(self, coldstart):
307
314
308
315
elasticapm .set_context (cloud_context , "cloud" )
309
316
elasticapm .set_context (service_context , "service" )
317
+ # faas doesn't actually belong in context, but we handle this in to_dict
318
+ elasticapm .set_context (faas , "faas" )
310
319
if message_context :
311
320
elasticapm .set_context (service_context , "message" )
312
321
self .client ._transport .add_metadata (metadata )
@@ -319,12 +328,13 @@ def get_data_from_request(event, capture_body=False, capture_headers=True):
319
328
result = {}
320
329
if capture_headers and "headers" in event :
321
330
result ["headers" ] = event ["headers" ]
322
- if "httpMethod" not in event :
331
+ method = nested_key (event , "requestContext" , "httpMethod" ) or nested_key (event , "requestContext" , "http" , "method" )
332
+ if not method :
323
333
# Not API Gateway
324
334
return result
325
335
326
- result ["method" ] = event [ "httpMethod" ]
327
- if event [ "httpMethod" ] in constants .HTTP_WITH_BODY and "body" in event :
336
+ result ["method" ] = method
337
+ if method in constants .HTTP_WITH_BODY and "body" in event :
328
338
body = event ["body" ]
329
339
if capture_body :
330
340
if event .get ("isBase64Encoded" ):
@@ -362,21 +372,23 @@ def get_url_dict(event):
362
372
Reconstruct URL from API Gateway
363
373
"""
364
374
headers = event .get ("headers" , {})
365
- proto = headers .get ("X-Forwarded-Proto" , " https" )
366
- host = headers .get ("Host" , "" )
367
- path = event . get ( "path" , "" )
368
- port = headers .get ("X-Forwarded-Port" )
369
- stage = "/" + event .get ("requestContext " , {}) .get ("stage" , "" )
375
+ protocol = headers .get ("X-Forwarded-Proto" , headers . get ( "x-forwarded-proto" , " https") )
376
+ host = headers .get ("Host" , headers . get ( "host" , "" ) )
377
+ stage = "/" + ( nested_key ( event , "requestContext" , "stage" ) or "" )
378
+ path = event .get ("path" , event . get ( "rawPath" , "" ). split ( stage )[ - 1 ] )
379
+ port = headers .get ("X-Forwarded-Port " , headers .get ("x-forwarded-port" ) )
370
380
query = ""
371
- if event .get ("queryStringParameters" ):
381
+ if "rawQueryString" in event :
382
+ query = event ["rawQueryString" ]
383
+ elif event .get ("queryStringParameters" ):
372
384
query = "?"
373
385
for k , v in compat .iteritems (event ["queryStringParameters" ]):
374
386
query += "{}={}" .format (k , v )
375
- url = proto + "://" + host + stage + path + query
387
+ url = protocol + "://" + host + stage + path + query
376
388
377
389
url_dict = {
378
390
"full" : encoding .keyword_field (url ),
379
- "protocol" : proto ,
391
+ "protocol" : protocol ,
380
392
"hostname" : encoding .keyword_field (host ),
381
393
"pathname" : encoding .keyword_field (stage + path ),
382
394
}
0 commit comments