Skip to content

Commit 898b630

Browse files
dtorokxrmx
authored andcommitted
Fixing w3c baggage support in opentelemetry-instrumentation-aws-lambda (open-telemetry#2589)
* Fixing w3c baggage support in opentelemetry-instrumentation-aws-lambda * Changelog update * Passing context not needed * Fixing unit test after rebase
1 parent 9dba7d5 commit 898b630

File tree

4 files changed

+115
-60
lines changed

4 files changed

+115
-60
lines changed

Diff for: CHANGELOG.md

+9
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
## Added
11+
12+
## Breaking changes
13+
1014
## Fixed
1115

1216
- `opentelemetry-instrumentation-aws-lambda` Avoid exception when a handler is not present.
@@ -17,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1721
([#2483](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2484))
1822
- `opentelemetry-instrumentation-fastapi` Fix fastapi-slim support
1923
([#2756](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2756))
24+
- `opentelemetry-instrumentation-aws-lambda` Fixing w3c baggage support
25+
([#2589](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2589))
2026

2127
## Version 1.26.0/0.47b0 (2024-07-23)
2228

@@ -113,7 +119,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
113119
([#2610](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2610))
114120
- `opentelemetry-instrumentation-asgi` Bugfix: Middleware did not set status code attribute on duration metrics for non-recording spans.
115121
([#2627](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2627))
122+
<<<<<<< HEAD
116123
- `opentelemetry-instrumentation-mysql` Add support for `mysql-connector-python` v9 ([#2751](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2751))
124+
=======
125+
>>>>>>> 5a623233 (Changelog update)
117126
118127
## Version 1.25.0/0.46b0 (2024-05-31)
119128

Diff for: instrumentation/opentelemetry-instrumentation-aws-lambda/src/opentelemetry/instrumentation/aws_lambda/__init__.py

+66-56
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ def custom_event_context_extractor(lambda_event):
7676

7777
from wrapt import wrap_function_wrapper
7878

79+
from opentelemetry import context as context_api
7980
from opentelemetry.context.context import Context
8081
from opentelemetry.instrumentation.aws_lambda.package import _instruments
8182
from opentelemetry.instrumentation.aws_lambda.version import __version__
@@ -303,66 +304,75 @@ def _instrumented_lambda_handler_call( # noqa pylint: disable=too-many-branches
303304
schema_url="https://opentelemetry.io/schemas/1.11.0",
304305
)
305306

306-
with tracer.start_as_current_span(
307-
name=orig_handler_name,
308-
context=parent_context,
309-
kind=span_kind,
310-
) as span:
311-
if span.is_recording():
312-
lambda_context = args[1]
313-
# NOTE: The specs mention an exception here, allowing the
314-
# `SpanAttributes.CLOUD_RESOURCE_ID` attribute to be set as a span
315-
# attribute instead of a resource attribute.
316-
#
317-
# See more:
318-
# https://github.com/open-telemetry/semantic-conventions/blob/main/docs/faas/aws-lambda.md#resource-detector
319-
span.set_attribute(
320-
SpanAttributes.CLOUD_RESOURCE_ID,
321-
lambda_context.invoked_function_arn,
322-
)
323-
span.set_attribute(
324-
SpanAttributes.FAAS_INVOCATION_ID,
325-
lambda_context.aws_request_id,
326-
)
327-
328-
# NOTE: `cloud.account.id` can be parsed from the ARN as the fifth item when splitting on `:`
329-
#
330-
# See more:
331-
# https://github.com/open-telemetry/semantic-conventions/blob/main/docs/faas/aws-lambda.md#all-triggers
332-
account_id = lambda_context.invoked_function_arn.split(":")[4]
333-
span.set_attribute(
334-
ResourceAttributes.CLOUD_ACCOUNT_ID,
335-
account_id,
336-
)
307+
token = context_api.attach(parent_context)
308+
try:
309+
with tracer.start_as_current_span(
310+
name=orig_handler_name,
311+
kind=span_kind,
312+
) as span:
313+
if span.is_recording():
314+
lambda_context = args[1]
315+
# NOTE: The specs mention an exception here, allowing the
316+
# `SpanAttributes.CLOUD_RESOURCE_ID` attribute to be set as a span
317+
# attribute instead of a resource attribute.
318+
#
319+
# See more:
320+
# https://github.com/open-telemetry/semantic-conventions/blob/main/docs/faas/aws-lambda.md#resource-detector
321+
span.set_attribute(
322+
SpanAttributes.CLOUD_RESOURCE_ID,
323+
lambda_context.invoked_function_arn,
324+
)
325+
span.set_attribute(
326+
SpanAttributes.FAAS_INVOCATION_ID,
327+
lambda_context.aws_request_id,
328+
)
337329

338-
exception = None
339-
result = None
340-
try:
341-
result = call_wrapped(*args, **kwargs)
342-
except Exception as exc: # pylint: disable=W0703
343-
exception = exc
344-
span.set_status(Status(StatusCode.ERROR))
345-
span.record_exception(exception)
346-
347-
# If the request came from an API Gateway, extract http attributes from the event
348-
# https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/instrumentation/aws-lambda.md#api-gateway
349-
# https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-server-semantic-conventions
350-
if isinstance(lambda_event, dict) and lambda_event.get(
351-
"requestContext"
352-
):
353-
span.set_attribute(SpanAttributes.FAAS_TRIGGER, "http")
354-
355-
if lambda_event.get("version") == "2.0":
356-
_set_api_gateway_v2_proxy_attributes(lambda_event, span)
357-
else:
358-
_set_api_gateway_v1_proxy_attributes(lambda_event, span)
359-
360-
if isinstance(result, dict) and result.get("statusCode"):
330+
# NOTE: `cloud.account.id` can be parsed from the ARN as the fifth item when splitting on `:`
331+
#
332+
# See more:
333+
# https://github.com/open-telemetry/semantic-conventions/blob/main/docs/faas/aws-lambda.md#all-triggers
334+
account_id = lambda_context.invoked_function_arn.split(
335+
":"
336+
)[4]
361337
span.set_attribute(
362-
SpanAttributes.HTTP_STATUS_CODE,
363-
result.get("statusCode"),
338+
ResourceAttributes.CLOUD_ACCOUNT_ID,
339+
account_id,
364340
)
365341

342+
exception = None
343+
result = None
344+
try:
345+
result = call_wrapped(*args, **kwargs)
346+
except Exception as exc: # pylint: disable=W0703
347+
exception = exc
348+
span.set_status(Status(StatusCode.ERROR))
349+
span.record_exception(exception)
350+
351+
# If the request came from an API Gateway, extract http attributes from the event
352+
# https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/instrumentation/aws-lambda.md#api-gateway
353+
# https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-server-semantic-conventions
354+
if isinstance(lambda_event, dict) and lambda_event.get(
355+
"requestContext"
356+
):
357+
span.set_attribute(SpanAttributes.FAAS_TRIGGER, "http")
358+
359+
if lambda_event.get("version") == "2.0":
360+
_set_api_gateway_v2_proxy_attributes(
361+
lambda_event, span
362+
)
363+
else:
364+
_set_api_gateway_v1_proxy_attributes(
365+
lambda_event, span
366+
)
367+
368+
if isinstance(result, dict) and result.get("statusCode"):
369+
span.set_attribute(
370+
SpanAttributes.HTTP_STATUS_CODE,
371+
result.get("statusCode"),
372+
)
373+
finally:
374+
context_api.detach(token)
375+
366376
now = time.time()
367377
_tracer_provider = tracer_provider or get_tracer_provider()
368378
if hasattr(_tracer_provider, "force_flush"):

Diff for: instrumentation/opentelemetry-instrumentation-aws-lambda/tests/mocks/lambda_function.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,14 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import json
16+
17+
from opentelemetry import baggage as baggage_api
18+
1519

1620
def handler(event, context):
17-
return "200 ok"
21+
baggage_content = dict(baggage_api.get_all().items())
22+
return json.dumps({"baggage_content": baggage_content})
1823

1924

2025
def rest_api_handler(event, context):

Diff for: instrumentation/opentelemetry-instrumentation-aws-lambda/tests/test_aws_lambda_instrumentation_manual.py

+34-3
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
14+
import json
1515
import os
1616
from dataclasses import dataclass
1717
from importlib import import_module, reload
1818
from typing import Any, Callable, Dict
1919
from unittest import mock
2020

2121
from opentelemetry import propagate
22+
from opentelemetry.baggage.propagation import W3CBaggagePropagator
2223
from opentelemetry.environment_variables import OTEL_PROPAGATORS
2324
from opentelemetry.instrumentation.aws_lambda import (
2425
_HANDLER,
@@ -79,6 +80,9 @@ def __init__(self, aws_request_id, invoked_function_arn):
7980
MOCK_W3C_TRACE_STATE_KEY = "vendor_specific_key"
8081
MOCK_W3C_TRACE_STATE_VALUE = "test_value"
8182

83+
MOCK_W3C_BAGGAGE_KEY = "baggage_key"
84+
MOCK_W3C_BAGGAGE_VALUE = "baggage_value"
85+
8286

8387
def mock_execute_lambda(event=None):
8488
"""Mocks the AWS Lambda execution.
@@ -97,7 +101,7 @@ def mock_execute_lambda(event=None):
97101

98102
module_name, handler_name = os.environ[_HANDLER].rsplit(".", 1)
99103
handler_module = import_module(module_name.replace("/", "."))
100-
getattr(handler_module, handler_name)(event, MOCK_LAMBDA_CONTEXT)
104+
return getattr(handler_module, handler_name)(event, MOCK_LAMBDA_CONTEXT)
101105

102106

103107
class TestAwsLambdaInstrumentor(TestBase):
@@ -181,6 +185,9 @@ class TestCase:
181185
expected_state_value: str = None
182186
expected_trace_state_len: int = 0
183187
propagators: str = "tracecontext"
188+
expected_baggage: str = None
189+
disable_aws_context_propagation: bool = False
190+
disable_aws_context_propagation_envvar: str = ""
184191

185192
def custom_event_context_extractor(lambda_event):
186193
return get_global_textmap().extract(lambda_event["foo"]["headers"])
@@ -266,6 +273,24 @@ def custom_event_context_extractor(lambda_event):
266273
expected_state_value=MOCK_W3C_TRACE_STATE_VALUE,
267274
xray_traceid=MOCK_XRAY_TRACE_CONTEXT_SAMPLED,
268275
),
276+
TestCase(
277+
name="baggage_propagation",
278+
custom_extractor=None,
279+
context={
280+
"headers": {
281+
TraceContextTextMapPropagator._TRACEPARENT_HEADER_NAME: MOCK_W3C_TRACE_CONTEXT_SAMPLED,
282+
TraceContextTextMapPropagator._TRACESTATE_HEADER_NAME: f"{MOCK_W3C_TRACE_STATE_KEY}={MOCK_W3C_TRACE_STATE_VALUE},foo=1,bar=2",
283+
W3CBaggagePropagator._BAGGAGE_HEADER_NAME: f"{MOCK_W3C_BAGGAGE_KEY}={MOCK_W3C_BAGGAGE_VALUE}",
284+
}
285+
},
286+
expected_traceid=MOCK_W3C_TRACE_ID,
287+
expected_parentid=MOCK_W3C_PARENT_SPAN_ID,
288+
expected_trace_state_len=3,
289+
expected_state_value=MOCK_W3C_TRACE_STATE_VALUE,
290+
xray_traceid=MOCK_XRAY_TRACE_CONTEXT_NOT_SAMPLED,
291+
expected_baggage=MOCK_W3C_BAGGAGE_VALUE,
292+
propagators="tracecontext,baggage",
293+
),
269294
]
270295
for test in tests:
271296

@@ -284,7 +309,9 @@ def custom_event_context_extractor(lambda_event):
284309
AwsLambdaInstrumentor().instrument(
285310
event_context_extractor=test.custom_extractor,
286311
)
287-
mock_execute_lambda(test.context)
312+
result = mock_execute_lambda(test.context)
313+
result = json.loads(result)
314+
288315
spans = self.memory_exporter.get_finished_spans()
289316
assert spans
290317
self.assertEqual(len(spans), 1)
@@ -305,6 +332,10 @@ def custom_event_context_extractor(lambda_event):
305332
parent_context.trace_state.get(MOCK_W3C_TRACE_STATE_KEY),
306333
test.expected_state_value,
307334
)
335+
self.assertEqual(
336+
result["baggage_content"].get(MOCK_W3C_BAGGAGE_KEY),
337+
test.expected_baggage,
338+
)
308339
self.assertTrue(parent_context.is_remote)
309340
self.memory_exporter.clear()
310341
AwsLambdaInstrumentor().uninstrument()

0 commit comments

Comments
 (0)