Skip to content

Commit 66fc7da

Browse files
test: add unit and e2e coverage for inferred aws.alb span
Cover ALB inferred-span creation, service mapping, inbound trace context, HTTP trigger tags, and full wrapper lifecycle including status code and parent/child linkage. FRSLES-851 / APMSVLS-542 Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent e6aa74c commit 66fc7da

3 files changed

Lines changed: 252 additions & 0 deletions

File tree

tests/test_tracing.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ def _wrap(*args, **kwargs):
112112
"api-gateway-websocket-disconnect",
113113
Context(trace_id=12345, span_id=67890, sampling_priority=2),
114114
),
115+
(
116+
"application-load-balancer",
117+
Context(trace_id=12345, span_id=67890, sampling_priority=2),
118+
),
115119
(
116120
"authorizer-request-api-gateway-v1",
117121
Context(
@@ -1953,6 +1957,133 @@ def test_remaps_specific_inferred_span_service_names_from_eventbridge_event(
19531957
self.assertEqual(span2.get_tag("operation_name"), "aws.eventbridge")
19541958
self.assertEqual(span2.service, "different.eventbridge.custom.event.sender")
19551959

1960+
def test_remaps_all_inferred_span_service_names_from_alb_event(self):
1961+
self.set_service_mapping({"lambda_alb": "new-name"})
1962+
with open(f"{event_samples}application-load-balancer.json") as event:
1963+
original_event = json.load(event)
1964+
1965+
ctx = get_mock_context()
1966+
ctx.aws_request_id = "123"
1967+
1968+
span1 = create_inferred_span(original_event, ctx)
1969+
self.assertEqual(span1.get_tag("operation_name"), "aws.alb")
1970+
self.assertEqual(span1.service, "new-name")
1971+
1972+
event2 = copy.deepcopy(original_event)
1973+
event2["headers"]["host"] = "different-alb.us-east-2.elb.amazonaws.com"
1974+
span2 = create_inferred_span(event2, ctx)
1975+
self.assertEqual(span2.get_tag("operation_name"), "aws.alb")
1976+
self.assertEqual(span2.service, "new-name")
1977+
1978+
def test_remaps_specific_inferred_span_service_names_from_alb_event(self):
1979+
host = "lambda-alb-123578498.us-east-2.elb.amazonaws.com"
1980+
self.set_service_mapping({host: "mapped-alb-service"})
1981+
with open(f"{event_samples}application-load-balancer.json") as event:
1982+
original_event = json.load(event)
1983+
1984+
ctx = get_mock_context()
1985+
ctx.aws_request_id = "123"
1986+
1987+
span1 = create_inferred_span(original_event, ctx)
1988+
self.assertEqual(span1.get_tag("operation_name"), "aws.alb")
1989+
self.assertEqual(span1.service, "mapped-alb-service")
1990+
1991+
event2 = copy.deepcopy(original_event)
1992+
event2["headers"]["host"] = "other-alb.us-east-2.elb.amazonaws.com"
1993+
span2 = create_inferred_span(event2, ctx)
1994+
self.assertEqual(span2.get_tag("operation_name"), "aws.alb")
1995+
self.assertEqual(span2.service, "other-alb.us-east-2.elb.amazonaws.com")
1996+
1997+
1998+
class TestAlbInferredSpan(unittest.TestCase):
1999+
ALB_SAMPLE = "application-load-balancer"
2000+
ALB_MULTIVALUE = "application-load-balancer-multivalue-headers"
2001+
ALB_HOST = "lambda-alb-123578498.us-east-2.elb.amazonaws.com"
2002+
ALB_USER_AGENT = (
2003+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
2004+
"(KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36"
2005+
)
2006+
2007+
def _load_event(self, sample_name):
2008+
with open(f"{event_samples}{sample_name}.json") as event_file:
2009+
return json.load(event_file)
2010+
2011+
def test_create_inferred_span_from_alb_event(self):
2012+
event = self._load_event(self.ALB_SAMPLE)
2013+
ctx = get_mock_context(aws_request_id="123")
2014+
2015+
span = create_inferred_span(event, ctx)
2016+
2017+
self.assertIsNotNone(span)
2018+
self.assertEqual(span.name, "aws.alb")
2019+
self.assertEqual(span.span_type, "http")
2020+
self.assertEqual(span.service, self.ALB_HOST)
2021+
self.assertEqual(span.resource, "GET /lambda")
2022+
self.assertEqual(span.get_tag("operation_name"), "aws.alb")
2023+
self.assertEqual(span.get_tag("span.kind"), "server")
2024+
self.assertEqual(span.get_tag("http.method"), "GET")
2025+
self.assertEqual(
2026+
span.get_tag("http.url"), f"http://{self.ALB_HOST}/lambda"
2027+
)
2028+
self.assertEqual(span.get_tag("http.useragent"), self.ALB_USER_AGENT)
2029+
self.assertEqual(span.get_tag("endpoint"), "/lambda")
2030+
self.assertEqual(span.get_tag("resource_names"), "GET /lambda")
2031+
self.assertEqual(span.get_tag("request_id"), "123")
2032+
self.assertEqual(span.get_tag("_inferred_span.synchronicity"), "sync")
2033+
self.assertEqual(span.get_tag("_inferred_span.tag_source"), "self")
2034+
self.assertEqual(span.get_metric("_dd._inferred_span"), 1.0)
2035+
self.assertEqual(
2036+
span.get_tag("target_group_arn"),
2037+
"arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/lambda-xyz/123abc",
2038+
)
2039+
2040+
def test_create_inferred_span_omits_tags_when_headers_missing(self):
2041+
event = self._load_event(self.ALB_SAMPLE)
2042+
del event["headers"]
2043+
event["httpMethod"] = None
2044+
event["path"] = None
2045+
2046+
span = create_inferred_span(event, get_mock_context())
2047+
2048+
self.assertIsNotNone(span)
2049+
self.assertNotIn("http.url", span.get_tags())
2050+
self.assertNotIn("http.method", span.get_tags())
2051+
self.assertNotIn("http.useragent", span.get_tags())
2052+
2053+
def test_multivalue_headers_subtype_returns_none(self):
2054+
event = self._load_event(self.ALB_MULTIVALUE)
2055+
span = create_inferred_span(event, get_mock_context())
2056+
self.assertIsNone(span)
2057+
2058+
@with_trace_propagation_style("datadog")
2059+
def test_inbound_datadog_context_parents_inferred_span(self):
2060+
event = self._load_event(self.ALB_SAMPLE)
2061+
ctx = get_mock_context()
2062+
2063+
parent_ctx, source, _ = extract_dd_trace_context(event, ctx)
2064+
set_dd_trace_py_root(source, merge_xray_traces=False)
2065+
span = create_inferred_span(event, ctx)
2066+
2067+
self.assertEqual(span.trace_id, parent_ctx.trace_id)
2068+
self.assertEqual(span.parent_id, parent_ctx.span_id)
2069+
2070+
def test_inbound_w3c_context_extracted_from_alb_event(self):
2071+
event = self._load_event(self.ALB_SAMPLE)
2072+
event["headers"] = {
2073+
"host": self.ALB_HOST,
2074+
"user-agent": self.ALB_USER_AGENT,
2075+
"x-forwarded-proto": "http",
2076+
"traceparent": "00-0000000000000000000000000000abcd-000000000000004d-01",
2077+
"tracestate": "dd=s:1",
2078+
}
2079+
2080+
ctx, source, _ = extract_dd_trace_context(event, get_mock_context())
2081+
2082+
self.assertIsNotNone(ctx)
2083+
self.assertEqual(source, TraceContextSource.EVENT)
2084+
self.assertEqual(ctx.trace_id, 0xABCD)
2085+
self.assertEqual(ctx.span_id, 0x4D)
2086+
19562087

19572088
class _Span(object):
19582089
def __init__(self, service, start, span_type, parent_name=None, tags=None):

tests/test_trigger.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,21 @@ def test_extract_trigger_tags_application_load_balancer(self):
434434
},
435435
)
436436

437+
def test_extract_trigger_tags_application_load_balancer_multivalue_headers(self):
438+
event_sample_source = "application-load-balancer-multivalue-headers"
439+
test_file = event_samples + event_sample_source + ".json"
440+
ctx = get_mock_context()
441+
with open(test_file, "r") as event:
442+
event = json.load(event)
443+
tags = extract_trigger_tags(event, ctx)
444+
445+
assert tags.get("function_trigger.event_source") == "application-load-balancer"
446+
assert tags.get("http.method") == "GET"
447+
assert tags.get("http.route") == "/lambda"
448+
# multi-value subtype has no single-value ``headers`` map
449+
assert "http.url" not in tags
450+
assert "http.useragent" not in tags
451+
437452
def test_extract_trigger_tags_cloudfront(self):
438453
event_sample_source = "cloudfront"
439454
test_file = event_samples + event_sample_source + ".json"

tests/test_wrapper.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,3 +1001,109 @@ def test_profiling_import_errors_caught(monkeypatch):
10011001
) # force ModuleNotFoundError
10021002
importlib.reload(wrapper)
10031003
assert not hasattr(wrapper.datadog_lambda_wrapper, "prof")
1004+
1005+
1006+
class TestAlbInferredSpanWrapper(unittest.TestCase):
1007+
"""End-to-end wrapper tests for the inferred aws.alb span (FRSLES-851)."""
1008+
1009+
def setUp(self):
1010+
patch("ddtrace.internal.remoteconfig.worker.RemoteConfigPoller").start()
1011+
wrapper.datadog_lambda_wrapper._force_wrap = True
1012+
1013+
patcher = patch("datadog.threadstats.reporters.HttpReporter.flush_distributions")
1014+
self.mock_flush_distributions = patcher.start()
1015+
self.addCleanup(patcher.stop)
1016+
1017+
patcher = patch("datadog_lambda.xray.send_segment")
1018+
self.mock_send_segment = patcher.start()
1019+
self.addCleanup(patcher.stop)
1020+
1021+
patcher = patch("datadog_lambda.wrapper.create_dd_dummy_metadata_subsegment")
1022+
self.mock_create_dd_dummy_metadata_subsegment = patcher.start()
1023+
self.addCleanup(patcher.stop)
1024+
1025+
with open("tests/event_samples/application-load-balancer.json") as f:
1026+
self.alb_event = json.load(f)
1027+
1028+
def _alb_response(self, status_code=200):
1029+
return {
1030+
"statusCode": status_code,
1031+
"statusDescription": f"{status_code} OK",
1032+
"headers": {"Content-Type": "application/json"},
1033+
"body": "{}",
1034+
"isBase64Encoded": False,
1035+
}
1036+
1037+
@patch("datadog_lambda.config.Config.trace_enabled", True)
1038+
@patch("datadog_lambda.config.Config.make_inferred_span", True)
1039+
def test_wrapper_emits_inferred_alb_span_with_http_tags(self):
1040+
@wrapper.datadog_lambda_wrapper
1041+
def lambda_handler(event, context):
1042+
return self._alb_response(200)
1043+
1044+
lambda_handler(self.alb_event, get_mock_context())
1045+
1046+
inferred = lambda_handler.inferred_span
1047+
execution = lambda_handler.span
1048+
1049+
self.assertIsNotNone(inferred)
1050+
self.assertEqual(inferred.name, "aws.alb")
1051+
self.assertEqual(inferred.get_tag("operation_name"), "aws.alb")
1052+
self.assertEqual(inferred.get_tag("http.method"), "GET")
1053+
self.assertEqual(
1054+
inferred.get_tag("http.url"),
1055+
"http://lambda-alb-123578498.us-east-2.elb.amazonaws.com/lambda",
1056+
)
1057+
self.assertEqual(inferred.get_tag("http.status_code"), "200")
1058+
self.assertEqual(inferred.get_tag("http.route"), "/lambda")
1059+
self.assertEqual(execution.parent_id, inferred.span_id)
1060+
self.assertEqual(execution.get_tag("http.status_code"), "200")
1061+
1062+
@patch("datadog_lambda.config.Config.trace_enabled", True)
1063+
@patch("datadog_lambda.config.Config.make_inferred_span", True)
1064+
def test_wrapper_inferred_alb_span_joins_inbound_datadog_context(self):
1065+
@wrapper.datadog_lambda_wrapper
1066+
def lambda_handler(event, context):
1067+
return self._alb_response(200)
1068+
1069+
lambda_handler(self.alb_event, get_mock_context())
1070+
1071+
inferred = lambda_handler.inferred_span
1072+
1073+
self.assertIsNotNone(inferred)
1074+
# Fixture carries x-datadog-trace-id=12345, x-datadog-parent-id=67890
1075+
self.assertEqual(inferred.trace_id, 12345)
1076+
self.assertEqual(inferred.parent_id, 67890)
1077+
1078+
@patch("datadog_lambda.config.Config.trace_enabled", True)
1079+
@patch("datadog_lambda.config.Config.make_inferred_span", True)
1080+
def test_wrapper_sets_error_on_inferred_alb_span_for_5xx(self):
1081+
@wrapper.datadog_lambda_wrapper
1082+
def lambda_handler(event, context):
1083+
return self._alb_response(502)
1084+
1085+
lambda_handler(self.alb_event, get_mock_context())
1086+
1087+
inferred = lambda_handler.inferred_span
1088+
execution = lambda_handler.span
1089+
1090+
self.assertEqual(inferred.get_tag("http.status_code"), "502")
1091+
self.assertEqual(execution.get_tag("http.status_code"), "502")
1092+
self.assertEqual(execution.error, 1)
1093+
1094+
@patch("datadog_lambda.config.Config.trace_enabled", True)
1095+
@patch("datadog_lambda.config.Config.make_inferred_span", True)
1096+
def test_wrapper_multivalue_alb_event_has_no_inferred_span(self):
1097+
with open(
1098+
"tests/event_samples/application-load-balancer-multivalue-headers.json"
1099+
) as f:
1100+
event = json.load(f)
1101+
1102+
@wrapper.datadog_lambda_wrapper
1103+
def lambda_handler(event, context):
1104+
return self._alb_response(200)
1105+
1106+
lambda_handler(event, get_mock_context())
1107+
1108+
self.assertIsNone(lambda_handler.inferred_span)
1109+
self.assertIsNotNone(lambda_handler.span)

0 commit comments

Comments
 (0)