Skip to content

Commit f9ba2ed

Browse files
authored
Merge branch 'main' into feat/support-eb-dsm-extraction
2 parents 24da389 + c50f73b commit f9ba2ed

12 files changed

Lines changed: 651 additions & 133 deletions

File tree

.github/workflows/update-snapshots.yml

Lines changed: 0 additions & 76 deletions
This file was deleted.

ci/datasources/regions.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ regions:
55
- code: "us-west-2"
66
- code: "af-south-1"
77
- code: "ap-east-1"
8+
- code: "ap-east-2"
89
- code: "ap-south-1"
910
- code: "ap-south-2"
1011
- code: "ap-southeast-1"
1112
- code: "ap-southeast-2"
1213
- code: "ap-southeast-3"
1314
- code: "ap-southeast-4"
1415
- code: "ap-southeast-5"
16+
- code: "ap-southeast-6"
1517
- code: "ap-southeast-7"
1618
- code: "ap-northeast-1"
1719
- code: "ap-northeast-2"

ci/publish_pypi.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ if [ -d "dist" ]; then
2828
fi
2929

3030
# Publish to pypi
31+
source venv/bin/activate
3132
poetry publish --build --username __token__ --password $PYPI_TOKEN

datadog_lambda/config.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,13 @@ def _resolve_env(self, key, default=None, cast=None, depends_on_tracing=False):
7575
add_span_pointers = _get_env("DD_BOTOCORE_ADD_SPAN_POINTERS", "true", as_bool)
7676
trace_extractor = _get_env("DD_TRACE_EXTRACTOR")
7777

78+
aws_service_representation_enabled = _get_env(
79+
"DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED", "true", as_bool
80+
)
81+
remove_integration_service_names_enabled = _get_env(
82+
"DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED", "false", as_bool
83+
)
84+
7885
enhanced_metrics_enabled = _get_env("DD_ENHANCED_METRICS", "true", as_bool)
7986

8087
flush_in_thread = _get_env("DD_FLUSH_IN_THREAD", "false", as_bool)

datadog_lambda/tracing.py

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
is_step_function_event,
4444
EventTypes,
4545
EventSubtypes,
46+
resolve_multivalue_headers,
4647
)
4748
from datadog_lambda.durable import extract_context_from_durable_execution
4849

@@ -207,7 +208,7 @@ def extract_context_from_http_event_or_context(
207208
if _is_context_complete(context):
208209
return context
209210

210-
headers = event.get("headers")
211+
headers = resolve_multivalue_headers(event)
211212
context = propagator.extract(headers)
212213

213214
if not _is_context_complete(context):
@@ -740,7 +741,9 @@ def extract_dd_trace_context(
740741
context = extract_context_from_request_header_or_context(
741742
event, lambda_context, event_source
742743
)
743-
elif isinstance(event, (set, dict)) and "headers" in event:
744+
elif isinstance(event, (set, dict)) and (
745+
"headers" in event or "multiValueHeaders" in event
746+
):
744747
context = extract_context_from_http_event_or_context(
745748
event, lambda_context, event_source, decode_authorizer_context
746749
)
@@ -919,6 +922,9 @@ def create_inferred_span(
919922
elif event_source.equals(EventTypes.LAMBDA_FUNCTION_URL):
920923
logger.debug("Function URL event detected. Inferring a span")
921924
return create_inferred_span_from_lambda_function_url_event(event, context)
925+
elif event_source.event_type == EventTypes.ALB:
926+
logger.debug("ALB event detected. Inferring a span")
927+
return create_inferred_span_from_alb_event(event, context)
922928
elif event_source.equals(
923929
EventTypes.API_GATEWAY, subtype=EventSubtypes.HTTP_API
924930
):
@@ -982,11 +988,14 @@ def determine_service_name(
982988
if mapped_service:
983989
return mapped_service
984990

991+
# When integration service names are removed, inferred (synthetic) spans use
992+
# the base service name (DD_SERVICE) instead of the AWS resource/instance
993+
# representation.
994+
if config.remove_integration_service_names_enabled and config.service:
995+
return config.service
996+
985997
# Check if AWS service representation is disabled
986-
aws_service_representation = os.environ.get(
987-
"DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED", ""
988-
).lower()
989-
if aws_service_representation in ("false", "0"):
998+
if not config.aws_service_representation_enabled:
990999
return fallback
9911000

9921001
# Use extracted_key if it exists and is not empty, otherwise use fallback
@@ -1034,6 +1043,54 @@ def create_inferred_span_from_lambda_function_url_event(event, context):
10341043
return span
10351044

10361045

1046+
def create_inferred_span_from_alb_event(event, context):
1047+
request_context = event.get("requestContext") or {}
1048+
elb = request_context.get("elb") or {}
1049+
target_group_arn = elb.get("targetGroupArn")
1050+
1051+
headers = resolve_multivalue_headers(event)
1052+
host = headers.get("host")
1053+
method = event.get("httpMethod")
1054+
path = event.get("path")
1055+
proto = headers.get("x-forwarded-proto", "http")
1056+
1057+
# ALB has no api id; key the service mapping off the load-balancer host and
1058+
# fall back to it when DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED is on.
1059+
service_name = determine_service_name(service_mapping, host, "lambda_alb", host)
1060+
1061+
http_url = "%s://%s%s" % (proto, host, path) if host and path is not None else None
1062+
if method and path is not None:
1063+
resource = f"{method} {path}"
1064+
else:
1065+
resource = method or path
1066+
1067+
tags = {
1068+
"operation_name": "aws.alb",
1069+
"span.kind": "server",
1070+
"http.method": method,
1071+
"http.url": http_url,
1072+
"http.useragent": headers.get("user-agent"),
1073+
"endpoint": path,
1074+
"resource_names": resource,
1075+
"request_id": context.aws_request_id,
1076+
"target_group_arn": target_group_arn,
1077+
}
1078+
# Drop tags we couldn't derive so the span never carries malformed values.
1079+
tags = {key: value for key, value in tags.items() if value is not None}
1080+
1081+
tracer.set_tags(_dd_origin)
1082+
# ALB events carry no request timestamp (unlike API GW requestTimeEpoch /
1083+
# Function URL timeEpoch), so the span starts at handler time.
1084+
span = tracer.trace(
1085+
"aws.alb", service=service_name, resource=resource, span_type="http"
1086+
)
1087+
InferredSpanInfo.set_tags(tags, tag_source="self", synchronicity="sync")
1088+
if span:
1089+
span.set_tags(tags)
1090+
span.set_metric(InferredSpanInfo.METRIC, 1.0)
1091+
return span
1092+
1093+
10371094
def is_api_gateway_invocation_async(event):
10381095
hdrs = event.get("headers")
10391096
if not hdrs:

datadog_lambda/trigger.py

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,39 @@ def get_event_source_arn(source: _EventSource, event: dict, context: Any) -> str
293293
return event_source_arn
294294

295295

296+
def resolve_multivalue_headers(event):
297+
"""
298+
Resolve request headers from single-value ``headers`` or ``multiValueHeaders``
299+
(first value per key, matching datadog-lambda-js).
300+
"""
301+
headers = event.get("headers")
302+
if isinstance(headers, dict) and headers:
303+
return headers
304+
305+
multi_value = event.get("multiValueHeaders")
306+
if not isinstance(multi_value, dict):
307+
return {}
308+
309+
resolved = {}
310+
for key, value in multi_value.items():
311+
if isinstance(value, list):
312+
if value:
313+
resolved[key] = value[0]
314+
elif isinstance(value, str):
315+
resolved[key] = value
316+
return resolved
317+
318+
319+
def _get_header_case_insensitive(headers, name):
320+
if not isinstance(headers, dict):
321+
return None
322+
name_lower = name.lower()
323+
for key, value in headers.items():
324+
if isinstance(key, str) and key.lower() == name_lower and value:
325+
return value
326+
return None
327+
328+
296329
def extract_http_tags(event):
297330
"""
298331
Extracts HTTP facet tags from the triggering event
@@ -324,19 +357,32 @@ def extract_http_tags(event):
324357
path = apigateway_v2_http.get("path")
325358
method = apigateway_v2_http.get("method")
326359

360+
elif request_context and request_context.get("elb"):
361+
# ALB events have no requestContext.stage; derive the URL from the
362+
# forwarded host/proto headers and the top-level path.
363+
alb_headers = resolve_multivalue_headers(event)
364+
host = alb_headers.get("host")
365+
if host:
366+
proto = alb_headers.get("x-forwarded-proto", "http")
367+
http_tags["http.url"] = proto + "://" + host
368+
369+
user_agent = alb_headers.get("user-agent")
370+
if user_agent:
371+
http_tags["http.useragent"] = user_agent
372+
373+
# ALB carries no route template, so use the request path as the route.
374+
if path:
375+
http_tags["http.route"] = path
376+
327377
if path:
328378
if http_tags.get("http.url"):
329379
http_tags["http.url"] += path
330380
if method:
331381
http_tags["http.method"] = method
332382

333-
# Safely get headers
334-
headers = event.get("headers", {})
335-
if not isinstance(headers, dict):
336-
headers = {}
337-
338-
if headers and headers.get("Referer"):
339-
http_tags["http.referer"] = headers.get("Referer")
383+
referer = _get_header_case_insensitive(resolve_multivalue_headers(event), "referer")
384+
if referer:
385+
http_tags["http.referer"] = referer
340386

341387
# Try to get `routeKey` from API GW v2; otherwise try to get `resource` from API GW v1
342388
route = event.get("routeKey") or event.get("resource")
@@ -415,6 +461,8 @@ def is_step_function_event(event):
415461
The actual event must contain "Execution", "StateMachine", and "State" fields.
416462
"""
417463
event = event.get("Payload", event)
464+
if not isinstance(event, dict):
465+
return False
418466

419467
# JSONPath style
420468
if "Execution" in event and "StateMachine" in event and "State" in event:

datadog_lambda/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "8.126.0.dev0"
1+
__version__ = "8.128.0.dev0"

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "datadog_lambda"
3-
version = "8.126.0.dev0"
3+
version = "8.128.0.dev0"
44
description = "The Datadog AWS Lambda Library"
55
authors = ["Datadog, Inc. <dev@datadoghq.com>"]
66
license = "Apache-2.0"

tests/integration/yarn.lock

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ bluebird@^3.7.2:
4343
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
4444

4545
brace-expansion@^1.1.7:
46-
version "1.1.14"
47-
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.14.tgz#d9de602370d91347cd9ddad1224d4fd701eb348b"
48-
integrity sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==
46+
version "1.1.18"
47+
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.18.tgz#3ce74d89885136be1535341f8c3d4425c29a5cab"
48+
integrity sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==
4949
dependencies:
5050
balanced-match "^1.0.0"
5151
concat-map "0.0.1"
@@ -366,7 +366,7 @@ log@^6.0.0:
366366
type "^2.7.3"
367367
uni-global "^1.0.0"
368368

369-
minimatch@^3.1.1, minimatch@^3.1.5:
369+
minimatch@^3.1.1:
370370
version "3.1.5"
371371
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.5.tgz#580c88f8d5445f2bd6aa8f3cadefa0de79fbd69e"
372372
integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==
@@ -564,10 +564,10 @@ shebang-regex@^1.0.0:
564564
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
565565
integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==
566566

567-
shell-quote@^1.8.1, shell-quote@^1.8.4:
568-
version "1.8.4"
569-
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.4.tgz#2edd9a4dcefc96649e2e2cb12f637b1f1d92a190"
570-
integrity sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==
567+
shell-quote@^1.8.1:
568+
version "1.10.0"
569+
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.10.0.tgz#482033e192e4f5c07151521ffa03400ec71b1b0f"
570+
integrity sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==
571571

572572
split2@^3.1.1:
573573
version "3.2.2"

0 commit comments

Comments
 (0)