3636import platform
3737import time
3838from typing import Optional
39+ from urllib .parse import urlencode
3940
4041import elasticapm
4142from elasticapm .base import Client
4445from elasticapm .utils .disttracing import TraceParent
4546from elasticapm .utils .logging import get_logger
4647
48+ SERVERLESS_HTTP_REQUEST = ("api" , "elb" )
49+
4750logger = get_logger ("elasticapm.serverless" )
4851
4952COLD_START = True
@@ -134,18 +137,25 @@ def __enter__(self):
134137 transaction_type = "request"
135138 transaction_name = os .environ .get ("AWS_LAMBDA_FUNCTION_NAME" , self .name )
136139
137- self .httpmethod = nested_key (self .event , "requestContext" , "httpMethod" ) or nested_key (
138- self .event , "requestContext" , "http" , "method"
140+ self .httpmethod = (
141+ nested_key (self .event , "requestContext" , "httpMethod" )
142+ or nested_key (self .event , "requestContext" , "http" , "method" )
143+ or nested_key (self .event , "httpMethod" )
139144 )
140- if self .httpmethod : # API Gateway
141- self .source = "api"
142- if nested_key (self .event , "requestContext" , "httpMethod" ):
145+
146+ if self .httpmethod : # http request
147+ if nested_key (self .event , "requestContext" , "elb" ):
148+ self .source = "elb"
149+ resource = nested_key (self .event , "path" )
150+ elif nested_key (self .event , "requestContext" , "httpMethod" ):
151+ self .source = "api"
143152 # API v1
144153 resource = "/{}{}" .format (
145154 nested_key (self .event , "requestContext" , "stage" ),
146155 nested_key (self .event , "requestContext" , "resourcePath" ),
147156 )
148157 else :
158+ self .source = "api"
149159 # API v2
150160 route_key = nested_key (self .event , "requestContext" , "routeKey" )
151161 route_key = f"/{ route_key } " if route_key .startswith ("$" ) else route_key .split (" " , 1 )[- 1 ]
@@ -170,7 +180,7 @@ def __enter__(self):
170180
171181 self .transaction = self .client .begin_transaction (transaction_type , trace_parent = trace_parent )
172182 elasticapm .set_transaction_name (transaction_name , override = False )
173- if self .source == "api" :
183+ if self .source in SERVERLESS_HTTP_REQUEST :
174184 elasticapm .set_context (
175185 lambda : get_data_from_request (
176186 self .event ,
@@ -199,7 +209,7 @@ def __exit__(self, exc_type, exc_val, exc_tb):
199209 elasticapm .set_transaction_result ("HTTP 5xx" , override = False )
200210 if exc_val :
201211 self .client .capture_exception (exc_info = (exc_type , exc_val , exc_tb ), handled = False )
202- if self .source == "api" :
212+ if self .source in SERVERLESS_HTTP_REQUEST :
203213 elasticapm .set_transaction_result ("HTTP 5xx" , override = False )
204214 elasticapm .set_transaction_outcome (http_status_code = 500 , override = False )
205215 elasticapm .set_context ({"status_code" : 500 }, "response" )
@@ -246,6 +256,17 @@ def set_metadata_and_context(self, coldstart: bool) -> None:
246256 cloud_context ["origin" ]["service" ] = {"name" : "api gateway" }
247257 cloud_context ["origin" ]["account" ] = {"id" : self .event ["requestContext" ]["accountId" ]}
248258 cloud_context ["origin" ]["provider" ] = "aws"
259+ elif self .source == "elb" :
260+ elb_target_group_arn = self .event ["requestContext" ]["elb" ]["targetGroupArn" ]
261+ faas ["trigger" ]["type" ] = "http"
262+ faas ["trigger" ]["request_id" ] = self .event ["headers" ]["x-amzn-trace-id" ]
263+ service_context ["origin" ] = {"name" : elb_target_group_arn .split (":" )[5 ]}
264+ service_context ["origin" ]["id" ] = elb_target_group_arn
265+ cloud_context ["origin" ] = {}
266+ cloud_context ["origin" ]["service" ] = {"name" : "elb" }
267+ cloud_context ["origin" ]["account" ] = {"id" : elb_target_group_arn .split (":" )[4 ]}
268+ cloud_context ["origin" ]["region" ] = elb_target_group_arn .split (":" )[3 ]
269+ cloud_context ["origin" ]["provider" ] = "aws"
249270 elif self .source == "sqs" :
250271 record = self .event ["Records" ][0 ]
251272 faas ["trigger" ]["type" ] = "pubsub"
@@ -281,7 +302,7 @@ def set_metadata_and_context(self, coldstart: bool) -> None:
281302 service_context ["origin" ]["service" ] = {"name" : "sns" }
282303 cloud_context ["origin" ] = {}
283304 cloud_context ["origin" ]["region" ] = record ["Sns" ]["TopicArn" ].split (":" )[3 ]
284- cloud_context ["origin" ]["account_id " ] = record ["Sns" ]["TopicArn" ].split (":" )[4 ]
305+ cloud_context ["origin" ]["account " ] = { "id" : record ["Sns" ]["TopicArn" ].split (":" )[4 ]}
285306 cloud_context ["origin" ]["provider" ] = "aws"
286307 message_context ["queue" ] = {"name" : service_context ["origin" ]["name" ]}
287308 if "Timestamp" in record ["Sns" ]:
@@ -354,7 +375,13 @@ def get_data_from_request(event: dict, capture_body: bool = False, capture_heade
354375 result = {}
355376 if capture_headers and "headers" in event :
356377 result ["headers" ] = event ["headers" ]
357- method = nested_key (event , "requestContext" , "httpMethod" ) or nested_key (event , "requestContext" , "http" , "method" )
378+
379+ method = (
380+ nested_key (event , "requestContext" , "httpMethod" )
381+ or nested_key (event , "requestContext" , "http" , "method" )
382+ or nested_key (event , "httpMethod" )
383+ )
384+
358385 if not method :
359386 # Not API Gateway
360387 return result
@@ -405,16 +432,23 @@ def get_url_dict(event: dict) -> dict:
405432 headers = event .get ("headers" , {})
406433 protocol = headers .get ("X-Forwarded-Proto" , headers .get ("x-forwarded-proto" , "https" ))
407434 host = headers .get ("Host" , headers .get ("host" , "" ))
408- stage = "/" + (nested_key (event , "requestContext" , "stage" ) or "" )
409- path = event .get ("path" , event .get ("rawPath" , "" ).split (stage )[- 1 ])
435+ stage = nested_key (event , "requestContext" , "stage" ) or ""
436+ raw_path = event .get ("rawPath" , "" )
437+ if stage :
438+ stage = "/" + stage
439+ raw_path = raw_path .split (stage )[- 1 ]
440+
441+ path = event .get ("path" , raw_path )
410442 port = headers .get ("X-Forwarded-Port" , headers .get ("x-forwarded-port" ))
411443 query = ""
412444 if "rawQueryString" in event :
413445 query = event ["rawQueryString" ]
414446 elif event .get ("queryStringParameters" ):
415- query = "?"
416- for k , v in event ["queryStringParameters" ].items ():
417- query += "{}={}" .format (k , v )
447+ if stage : # api requires parameters encoding to build correct url
448+ query = "?" + urlencode (event ["queryStringParameters" ])
449+ else : # for elb we do not have the stage
450+ query = "?" + "&" .join (["{}={}" .format (k , v ) for k , v in event ["queryStringParameters" ].items ()])
451+
418452 url = protocol + "://" + host + stage + path + query
419453
420454 url_dict = {
0 commit comments