Skip to content

Commit 69b52ab

Browse files
committed
fix(aws-lambda): support ALB multiValueHeaders
1 parent 2c6fd20 commit 69b52ab

2 files changed

Lines changed: 69 additions & 8 deletions

File tree

instrumentation/opentelemetry-instrumentation-aws-lambda/src/opentelemetry/instrumentation/aws_lambda/__init__.py

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -157,20 +157,40 @@ def _default_event_context_extractor(lambda_event: Any) -> Context:
157157
Returns:
158158
A Context with configuration found in the event.
159159
"""
160-
headers = None
161-
try:
162-
headers = lambda_event["headers"]
163-
except (TypeError, KeyError):
160+
headers = _extract_http_headers(lambda_event)
161+
if not headers:
164162
logger.debug(
165163
"Extracting context from Lambda Event failed: either enable X-Ray active tracing or configure API Gateway to trigger this Lambda function as a pure proxy. Otherwise, generated spans will have an invalid (empty) parent context."
166164
)
167-
if not isinstance(headers, dict):
168-
headers = {}
169165
return get_global_textmap().extract(
170166
CIDict(headers),
171167
)
172168

173169

170+
def _extract_http_headers(lambda_event: Any) -> dict[str, Any]:
171+
try:
172+
headers = lambda_event["headers"]
173+
except (TypeError, KeyError):
174+
headers = None
175+
176+
if isinstance(headers, dict):
177+
return headers
178+
179+
try:
180+
multi_value_headers = lambda_event["multiValueHeaders"]
181+
except (TypeError, KeyError):
182+
return {}
183+
184+
if not isinstance(multi_value_headers, dict):
185+
return {}
186+
187+
normalized_headers = {}
188+
for key, values in multi_value_headers.items():
189+
if isinstance(values, list) and values:
190+
normalized_headers[key] = values[0]
191+
return normalized_headers
192+
193+
174194
def _determine_parent_context(
175195
lambda_event: Any,
176196
event_context_extractor: Callable[[Any], Context],
@@ -207,8 +227,9 @@ def _set_api_gateway_v1_proxy_attributes(
207227
"""
208228
span.set_attribute(HTTP_METHOD, lambda_event.get("httpMethod"))
209229

210-
if lambda_event.get("headers"):
211-
headers = CIDict(lambda_event["headers"])
230+
headers = _extract_http_headers(lambda_event)
231+
if headers:
232+
headers = CIDict(headers)
212233
if "User-Agent" in headers:
213234
span.set_attribute(
214235
HTTP_USER_AGENT,

instrumentation/opentelemetry-instrumentation-aws-lambda/tests/test_aws_lambda_instrumentation_manual.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import logging
55
import os
6+
from copy import deepcopy
67
from dataclasses import dataclass
78
from importlib import import_module, reload
89
from typing import Any, Callable, Dict
@@ -741,9 +742,48 @@ def test_alb_multi_value_header_event_sets_attributes(self):
741742
{
742743
FAAS_TRIGGER: "http",
743744
HTTP_METHOD: "GET",
745+
HTTP_SCHEME: "https",
746+
HTTP_USER_AGENT: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6)",
747+
NET_HOST_NAME: "lambda-846800462-us-east-2.elb.amazonaws.com",
744748
},
745749
)
746750

751+
def test_alb_multi_value_header_event_extracts_parent_context(self):
752+
test_env_patch = mock.patch.dict(
753+
"os.environ",
754+
{
755+
**os.environ,
756+
_X_AMZN_TRACE_ID: MOCK_XRAY_TRACE_CONTEXT_NOT_SAMPLED,
757+
OTEL_PROPAGATORS: "tracecontext",
758+
},
759+
)
760+
test_env_patch.start()
761+
reload(propagate)
762+
763+
AwsLambdaInstrumentor().instrument()
764+
765+
event = deepcopy(MOCK_LAMBDA_ALB_MULTI_VALUE_HEADER_EVENT)
766+
event["multiValueHeaders"][
767+
TraceContextTextMapPropagator._TRACEPARENT_HEADER_NAME
768+
] = [MOCK_W3C_TRACE_CONTEXT_SAMPLED]
769+
770+
mock_execute_lambda(event)
771+
772+
spans = self.memory_exporter.get_finished_spans()
773+
self.assertEqual(len(spans), 1)
774+
775+
span, *_ = spans
776+
self.assertEqual(span.get_span_context().trace_id, MOCK_W3C_TRACE_ID)
777+
778+
parent_context = span.parent
779+
self.assertEqual(
780+
parent_context.trace_id, span.get_span_context().trace_id
781+
)
782+
self.assertEqual(parent_context.span_id, MOCK_W3C_PARENT_SPAN_ID)
783+
self.assertTrue(parent_context.is_remote)
784+
785+
test_env_patch.stop()
786+
747787
def test_dynamo_db_event_sets_attributes(self):
748788
AwsLambdaInstrumentor().instrument()
749789

0 commit comments

Comments
 (0)