Skip to content

Commit 6c561ae

Browse files
authored
Support lambda function URLs (#220)
* feat: Function URLs * feat: Use new standard for tagging service and resource names * feat: Use the right path * feat: black
1 parent 5ffab8e commit 6c561ae

File tree

2 files changed

+80
-8
lines changed

2 files changed

+80
-8
lines changed

datadog_lambda/tracing.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,9 @@ def create_inferred_span(event, context):
506506
):
507507
logger.debug("API Gateway event detected. Inferring a span")
508508
return create_inferred_span_from_api_gateway_event(event, context)
509+
elif event_source.equals(EventTypes.LAMBDA_FUNCTION_URL):
510+
logger.debug("Function URL event detected. Inferring a span")
511+
return create_inferred_span_from_lambda_function_url_event(event, context)
509512
elif event_source.equals(
510513
EventTypes.API_GATEWAY, subtype=EventSubtypes.HTTP_API
511514
):
@@ -546,6 +549,38 @@ def create_inferred_span(event, context):
546549
return None
547550

548551

552+
def create_inferred_span_from_lambda_function_url_event(event, context):
553+
request_context = event["requestContext"]
554+
domain = request_context["domainName"]
555+
method = request_context["http"]["method"]
556+
path = request_context["http"]["path"]
557+
resource = "{0} {1}".format(method, path)
558+
tags = {
559+
"operation_name": "aws.lambda.url",
560+
"http.url": domain + path,
561+
"endpoint": path,
562+
"http.method": method,
563+
"resource_names": domain + path,
564+
"request_id": context.aws_request_id,
565+
}
566+
request_time_epoch = request_context["timeEpoch"]
567+
args = {
568+
"service": domain,
569+
"resource": resource,
570+
"span_type": "http",
571+
}
572+
tracer.set_tags(
573+
{"_dd.origin": "lambda"}
574+
) # function urls don't count as lambda_inferred,
575+
# because they're in the same service as the inferring lambda function
576+
span = tracer.trace("aws.lambda.url", **args)
577+
InferredSpanInfo.set_tags(tags, tag_source="self", synchronicity="sync")
578+
if span:
579+
span.set_tags(tags)
580+
span.start = request_time_epoch / 1000
581+
return span
582+
583+
549584
def is_api_gateway_invocation_async(event):
550585
return (
551586
"headers" in event

datadog_lambda/trigger.py

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class EventTypes(_stringTypedEnum):
3535
CLOUDFRONT = "cloudfront"
3636
DYNAMODB = "dynamodb"
3737
KINESIS = "kinesis"
38+
LAMBDA_FUNCTION_URL = "lambda-function-url"
3839
S3 = "s3"
3940
SNS = "sns"
4041
SQS = "sqs"
@@ -115,6 +116,10 @@ def parse_event_source(event: dict) -> _EventSource:
115116

116117
request_context = event.get("requestContext")
117118
if request_context and request_context.get("stage"):
119+
if "domainName" in request_context and detect_lambda_function_url_domain(
120+
request_context.get("domainName")
121+
):
122+
return _EventSource(EventTypes.LAMBDA_FUNCTION_URL)
118123
event_source = _EventSource(EventTypes.API_GATEWAY)
119124
if "httpMethod" in event:
120125
event_source.subtype = EventSubtypes.API_GATEWAY
@@ -160,6 +165,14 @@ def parse_event_source(event: dict) -> _EventSource:
160165
return event_source
161166

162167

168+
def detect_lambda_function_url_domain(domain: str) -> bool:
169+
# e.g. "etsn5fibjr.lambda-url.eu-south-1.amazonaws.com"
170+
domain_parts = domain.split(".")
171+
if len(domain_parts) < 2:
172+
return False
173+
return domain_parts[1] == "lambda-url"
174+
175+
163176
def parse_event_source_arn(source: _EventSource, event: dict, context: Any) -> str:
164177
"""
165178
Parses the trigger event for an available ARN. If an ARN field is not provided
@@ -186,6 +199,18 @@ def parse_event_source_arn(source: _EventSource, event: dict, context: Any) -> s
186199
aws_arn, account_id, distribution_id
187200
)
188201

202+
# e.g. arn:aws:lambda:<region>:<account_id>:url:<function-name>:<function-qualifier>
203+
if source.equals(EventTypes.LAMBDA_FUNCTION_URL):
204+
function_name = ""
205+
if len(split_function_arn) >= 7:
206+
function_name = split_function_arn[6]
207+
function_arn = f"arn:aws:lambda:{region}:{account_id}:url:{function_name}"
208+
function_qualifier = ""
209+
if len(split_function_arn) >= 8:
210+
function_qualifier = split_function_arn[7]
211+
function_arn = function_arn + f":{function_qualifier}"
212+
return function_arn
213+
189214
# e.g. arn:aws:apigateway:us-east-1::/restapis/xyz123/stages/default
190215
if source.event_type == EventTypes.API_GATEWAY:
191216
request_context = event.get("requestContext")
@@ -275,23 +300,35 @@ def extract_trigger_tags(event: dict, context: Any) -> dict:
275300
if event_source_arn:
276301
trigger_tags["function_trigger.event_source_arn"] = event_source_arn
277302

278-
if event_source.event_type in [EventTypes.API_GATEWAY, EventTypes.ALB]:
303+
if event_source.event_type in [
304+
EventTypes.API_GATEWAY,
305+
EventTypes.ALB,
306+
EventTypes.LAMBDA_FUNCTION_URL,
307+
]:
279308
trigger_tags.update(extract_http_tags(event))
280309

281310
return trigger_tags
282311

283312

284313
def extract_http_status_code_tag(trigger_tags, response):
285314
"""
286-
If the Lambda was triggered by API Gateway or ALB add the returned status code
315+
If the Lambda was triggered by API Gateway, Lambda Function URL, or ALB add the returned status code
287316
as a tag to the function execution span.
288317
"""
289-
is_http_trigger = trigger_tags and (
290-
trigger_tags.get("function_trigger.event_source") == "api-gateway"
291-
or trigger_tags.get("function_trigger.event_source")
292-
== "application-load-balancer"
293-
)
294-
if not is_http_trigger:
318+
if trigger_tags is None:
319+
return
320+
str_event_source = trigger_tags.get("function_trigger.event_source")
321+
# it would be cleaner if each event type was a constant object that
322+
# knew some properties about itself like this.
323+
str_http_triggers = [
324+
et.value
325+
for et in [
326+
EventTypes.API_GATEWAY,
327+
EventTypes.LAMBDA_FUNCTION_URL,
328+
EventTypes.ALB,
329+
]
330+
]
331+
if str_event_source not in str_http_triggers:
295332
return
296333

297334
status_code = "200"

0 commit comments

Comments
 (0)