Skip to content

Commit 1324488

Browse files
authored
Reload propagators for distro config + Reorder propagators (#421)
## Issue The [`OTEL_PROPAGATORS` configured in the ADOT](https://github.com/aws-observability/aws-otel-python-instrumentation/blob/af71c4a100f0ae9c8d378db8487988344485b0c3/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/aws_opentelemetry_distro.py#L73) do not actually take effect. The configured propagators are still `tracecontext` and `baggage` which are the default ones configured by OTel SDK. This causes ADOT to ignore the `x-amzn-trace-id` trace header causing broken traces if there is only XRay format trace being propagated. ## Root Cause - The [`opentelemetry.propagate` module](https://github.com/open-telemetry/opentelemetry-python/blob/main/opentelemetry-api/src/opentelemetry/propagate/__init__.py) has [the logic to load the propagators](https://github.com/open-telemetry/opentelemetry-python/blob/0a2df97a72561552fb72e41c6fd092578a5f43e9/opentelemetry-api/src/opentelemetry/propagate/__init__.py#L124-L163) configured via the `OTEL_PROPAGATORS` env variable. If the env variable is not set, then it sets up the default propagators. - This module is loaded and run very early in the OTel startup logic even before the ADOT distro code is run which sets the `OTEL_PROPAGATORS` env variable. This point is too late to modify the OTEL_PROPAGATORS env variable. ## Solution Set the `OTEL_PROPAGATORS` env variable in ADOT distro **and** reload the `opentelemetry.propagate` module (using [importlib.reload](https://docs.python.org/3/library/importlib.html#importlib.reload)) which will force it to reinitialize the intended propagators. ## Related Note With this fix, the ADOT configured propagators will take effect, which currently does not include `baggage` and will break some existing functionality that depends on baggage. So I'm also also updating the propagators set by the ADOT to: - include `baggage` - drop the B3 propagators as [ADOT JS did](aws-observability/aws-otel-js-instrumentation@82e563c#diff-02388c43665de519f206132ad186755c563709c77f6de9f919a4e70fc51abc25) - reorder to `baggage, xray, tracecontext`. This will be the order we follow for other ADOT SDKs too ([JS already in progress](aws-observability/aws-otel-js-instrumentation#210)). By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
1 parent af71c4a commit 1324488

File tree

3 files changed

+70
-6
lines changed

3 files changed

+70
-6
lines changed

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/aws_opentelemetry_distro.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
3+
import importlib
34
import os
45
import sys
56
from logging import Logger, getLogger
@@ -18,6 +19,7 @@
1819
OTEL_TRACES_SAMPLER,
1920
)
2021
from amazon.opentelemetry.distro.patches._instrumentation_patch import apply_instrumentation_patches
22+
from opentelemetry import propagate
2123
from opentelemetry.distro import OpenTelemetryDistro
2224
from opentelemetry.environment_variables import OTEL_PROPAGATORS, OTEL_PYTHON_ID_GENERATOR
2325
from opentelemetry.sdk.environment_variables import (
@@ -70,7 +72,16 @@ def _configure(self, **kwargs):
7072

7173
os.environ.setdefault(OTEL_EXPORTER_OTLP_PROTOCOL, "http/protobuf")
7274

73-
os.environ.setdefault(OTEL_PROPAGATORS, "xray,tracecontext,b3,b3multi")
75+
if os.environ.get(OTEL_PROPAGATORS, None) is None:
76+
# xray is set after baggage in case xray propagator depends on the result of the baggage header extraction.
77+
os.environ.setdefault(OTEL_PROPAGATORS, "baggage,xray,tracecontext")
78+
# We need to explicitly reload the opentelemetry.propagate module here
79+
# because this module initializes the default propagators when it loads very early in the chain.
80+
# Without reloading the OTEL_PROPAGATOR config from this distro won't take any effect.
81+
# It's a hack from our end until OpenTelemetry fixes this behavior for distros to
82+
# override the default propagators.
83+
importlib.reload(propagate)
84+
7485
os.environ.setdefault(OTEL_PYTHON_ID_GENERATOR, "xray")
7586
os.environ.setdefault(
7687
OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION, "base2_exponential_bucket_histogram"

aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_opentelementry_configurator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1262,7 +1262,7 @@ def validate_distro_environ():
12621262
tc.assertEqual(
12631263
"base2_exponential_bucket_histogram", os.environ.get("OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION")
12641264
)
1265-
tc.assertEqual("xray,tracecontext,b3,b3multi", os.environ.get("OTEL_PROPAGATORS"))
1265+
tc.assertEqual("baggage,xray,tracecontext", os.environ.get("OTEL_PROPAGATORS"))
12661266
tc.assertEqual("xray", os.environ.get("OTEL_PYTHON_ID_GENERATOR"))
12671267

12681268
# Not set

aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_opentelemetry_distro.py

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
3+
import importlib
34
import os
5+
import sys
46
from importlib.metadata import PackageNotFoundError, version
57
from unittest import TestCase
68
from unittest.mock import patch
79

810
from amazon.opentelemetry.distro.aws_opentelemetry_distro import AwsOpenTelemetryDistro
11+
from opentelemetry import propagate
12+
from opentelemetry.propagators.composite import CompositePropagator
913

1014

1115
class TestAwsOpenTelemetryDistro(TestCase):
@@ -40,6 +44,9 @@ def setUp(self):
4044
if var in os.environ:
4145
del os.environ[var]
4246

47+
# Preserve the original sys.path
48+
self.original_sys_path = sys.path.copy()
49+
4350
def tearDown(self):
4451
# Clear all env vars first
4552
for var in self.env_vars_to_check:
@@ -50,6 +57,9 @@ def tearDown(self):
5057
for var, value in self.env_vars_to_restore.items():
5158
os.environ[var] = value
5259

60+
# Restore the original sys.path
61+
sys.path[:] = self.original_sys_path
62+
5363
def test_package_available(self):
5464
try:
5565
version("aws-opentelemetry-distro")
@@ -65,7 +75,7 @@ def test_configure_sets_default_values(self, mock_super_configure, mock_apply_pa
6575

6676
# Check that default values are set
6777
self.assertEqual(os.environ.get("OTEL_EXPORTER_OTLP_PROTOCOL"), "http/protobuf")
68-
self.assertEqual(os.environ.get("OTEL_PROPAGATORS"), "xray,tracecontext,b3,b3multi")
78+
self.assertEqual(os.environ.get("OTEL_PROPAGATORS"), "baggage,xray,tracecontext")
6979
self.assertEqual(os.environ.get("OTEL_PYTHON_ID_GENERATOR"), "xray")
7080
self.assertEqual(
7181
os.environ.get("OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION"),
@@ -188,16 +198,15 @@ def test_configure_preserves_existing_env_vars(
188198
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.apply_instrumentation_patches")
189199
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.OpenTelemetryDistro._configure")
190200
@patch("os.getcwd")
191-
@patch("sys.path", new_callable=list)
192-
def test_configure_adds_cwd_to_sys_path(self, mock_sys_path, mock_getcwd, mock_super_configure, mock_apply_patches):
201+
def test_configure_adds_cwd_to_sys_path(self, mock_getcwd, mock_super_configure, mock_apply_patches):
193202
"""Test that _configure adds current working directory to sys.path"""
194203
mock_getcwd.return_value = "/test/working/directory"
195204

196205
distro = AwsOpenTelemetryDistro()
197206
distro._configure()
198207

199208
# Check that cwd was added to sys.path
200-
self.assertIn("/test/working/directory", mock_sys_path)
209+
self.assertIn("/test/working/directory", sys.path)
201210

202211
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.get_aws_region")
203212
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.is_agent_observability_enabled")
@@ -224,3 +233,47 @@ def test_configure_with_agent_observability_endpoints_already_set(
224233
# And exporters are still set to otlp
225234
self.assertEqual(os.environ.get("OTEL_TRACES_EXPORTER"), "otlp")
226235
self.assertEqual(os.environ.get("OTEL_LOGS_EXPORTER"), "otlp")
236+
237+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.apply_instrumentation_patches")
238+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.OpenTelemetryDistro._configure")
239+
def test_user_defined_propagators(self, mock_super_configure, mock_apply_patches):
240+
"""Test that user-defined propagators are respected"""
241+
# Set user-defined propagators
242+
os.environ["OTEL_PROPAGATORS"] = "xray"
243+
244+
# Force the reload of the propagate module otherwise the above environment
245+
# variable doesn't taker effect.
246+
importlib.reload(propagate)
247+
248+
distro = AwsOpenTelemetryDistro()
249+
distro._configure()
250+
251+
# Verify that user-defined propagators are preserved
252+
propagators = propagate.get_global_textmap()
253+
self.assertTrue(isinstance(propagators, CompositePropagator))
254+
expected_propagators = ["AwsXRayPropagator"]
255+
individual_propagators = propagators._propagators
256+
self.assertEqual(1, len(individual_propagators))
257+
actual_propagators = []
258+
for prop in individual_propagators:
259+
actual_propagators.append(type(prop).__name__)
260+
self.assertEqual(expected_propagators, actual_propagators)
261+
262+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.apply_instrumentation_patches")
263+
@patch("amazon.opentelemetry.distro.aws_opentelemetry_distro.OpenTelemetryDistro._configure")
264+
def test_otel_propagators_added_when_not_user_defined(self, mock_super_configure, mock_apply_patches):
265+
distro = AwsOpenTelemetryDistro()
266+
distro._configure()
267+
268+
# Verify that the propagators are set correctly by ADOT
269+
propagators = propagate.get_global_textmap()
270+
271+
self.assertTrue(isinstance(propagators, CompositePropagator))
272+
273+
expected_propagators = ["W3CBaggagePropagator", "AwsXRayPropagator", "TraceContextTextMapPropagator"]
274+
individual_propagators = propagators._propagators
275+
self.assertEqual(3, len(individual_propagators))
276+
actual_propagators = []
277+
for prop in individual_propagators:
278+
actual_propagators.append(type(prop).__name__)
279+
self.assertEqual(expected_propagators, actual_propagators)

0 commit comments

Comments
 (0)