Skip to content

Commit 42e8f8f

Browse files
tsloughterocelotl
andauthored
botocore: always use x-ray for http header injection (#1741)
Co-authored-by: Diego Hurtado <[email protected]>
1 parent 530650d commit 42e8f8f

File tree

4 files changed

+68
-34
lines changed

4 files changed

+68
-34
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3030
- `opentelemetry-instrumentation-logging` Add `otelTraceSampled` to instrumetation-logging
3131
([#1773](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1773))
3232

33+
### Changed
34+
35+
- `opentelemetry-instrumentation-botocore` now uses the AWS X-Ray propagator by
36+
default
37+
([#1741](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1741))
3338

3439
### Fixed
3540

instrumentation/opentelemetry-instrumentation-botocore/pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ dependencies = [
2828
"opentelemetry-api ~= 1.12",
2929
"opentelemetry-instrumentation == 0.40b0.dev",
3030
"opentelemetry-semantic-conventions == 0.40b0.dev",
31+
"opentelemetry-propagator-aws-xray == 1.0.1",
3132
]
3233

3334
[project.optional-dependencies]

instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/__init__.py

+20-10
Original file line numberDiff line numberDiff line change
@@ -101,22 +101,14 @@ def response_hook(span, service_name, operation_name, result):
101101
_SUPPRESS_INSTRUMENTATION_KEY,
102102
unwrap,
103103
)
104-
from opentelemetry.propagate import inject
104+
from opentelemetry.propagators.aws.aws_xray_propagator import AwsXRayPropagator
105105
from opentelemetry.semconv.trace import SpanAttributes
106106
from opentelemetry.trace import get_tracer
107107
from opentelemetry.trace.span import Span
108108

109109
logger = logging.getLogger(__name__)
110110

111111

112-
# pylint: disable=unused-argument
113-
def _patched_endpoint_prepare_request(wrapped, instance, args, kwargs):
114-
request = args[0]
115-
headers = request.headers
116-
inject(headers)
117-
return wrapped(*args, **kwargs)
118-
119-
120112
class BotocoreInstrumentor(BaseInstrumentor):
121113
"""An instrumentor for Botocore.
122114
@@ -127,6 +119,7 @@ def __init__(self):
127119
super().__init__()
128120
self.request_hook = None
129121
self.response_hook = None
122+
self.propagator = AwsXRayPropagator()
130123

131124
def instrumentation_dependencies(self) -> Collection[str]:
132125
return _instruments
@@ -140,6 +133,10 @@ def _instrument(self, **kwargs):
140133
self.request_hook = kwargs.get("request_hook")
141134
self.response_hook = kwargs.get("response_hook")
142135

136+
propagator = kwargs.get("propagator")
137+
if propagator is not None:
138+
self.propagator = propagator
139+
143140
wrap_function_wrapper(
144141
"botocore.client",
145142
"BaseClient._make_api_call",
@@ -149,13 +146,26 @@ def _instrument(self, **kwargs):
149146
wrap_function_wrapper(
150147
"botocore.endpoint",
151148
"Endpoint.prepare_request",
152-
_patched_endpoint_prepare_request,
149+
self._patched_endpoint_prepare_request,
153150
)
154151

155152
def _uninstrument(self, **kwargs):
156153
unwrap(BaseClient, "_make_api_call")
157154
unwrap(Endpoint, "prepare_request")
158155

156+
# pylint: disable=unused-argument
157+
def _patched_endpoint_prepare_request(
158+
self, wrapped, instance, args, kwargs
159+
):
160+
request = args[0]
161+
headers = request.headers
162+
163+
# Only the x-ray header is propagated by AWS services. Using any
164+
# other propagator will lose the trace context.
165+
self.propagator.inject(headers)
166+
167+
return wrapped(*args, **kwargs)
168+
159169
# pylint: disable=too-many-branches
160170
def _patched_api_call(self, original_func, instance, args, kwargs):
161171
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):

instrumentation/opentelemetry-instrumentation-botocore/tests/test_botocore_instrumentation.py

+42-24
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@
3636
from opentelemetry.instrumentation.botocore import BotocoreInstrumentor
3737
from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY
3838
from opentelemetry.propagate import get_global_textmap, set_global_textmap
39+
from opentelemetry.propagators.aws.aws_xray_propagator import TRACE_HEADER_KEY
3940
from opentelemetry.semconv.trace import SpanAttributes
4041
from opentelemetry.test.mock_textmap import MockTextMapPropagator
4142
from opentelemetry.test.test_base import TestBase
43+
from opentelemetry.trace.span import format_span_id, format_trace_id
4244

4345
_REQUEST_ID_REGEX_MATCH = r"[A-Z0-9]{52}"
4446

@@ -225,27 +227,21 @@ def test_unpatch(self):
225227
@mock_ec2
226228
def test_uninstrument_does_not_inject_headers(self):
227229
headers = {}
228-
previous_propagator = get_global_textmap()
229-
try:
230-
set_global_textmap(MockTextMapPropagator())
231230

232-
def intercept_headers(**kwargs):
233-
headers.update(kwargs["request"].headers)
231+
def intercept_headers(**kwargs):
232+
headers.update(kwargs["request"].headers)
234233

235-
ec2 = self._make_client("ec2")
234+
ec2 = self._make_client("ec2")
236235

237-
BotocoreInstrumentor().uninstrument()
236+
BotocoreInstrumentor().uninstrument()
238237

239-
ec2.meta.events.register_first(
240-
"before-send.ec2.DescribeInstances", intercept_headers
241-
)
242-
with self.tracer_provider.get_tracer("test").start_span("parent"):
243-
ec2.describe_instances()
238+
ec2.meta.events.register_first(
239+
"before-send.ec2.DescribeInstances", intercept_headers
240+
)
241+
with self.tracer_provider.get_tracer("test").start_span("parent"):
242+
ec2.describe_instances()
244243

245-
self.assertNotIn(MockTextMapPropagator.TRACE_ID_KEY, headers)
246-
self.assertNotIn(MockTextMapPropagator.SPAN_ID_KEY, headers)
247-
finally:
248-
set_global_textmap(previous_propagator)
244+
self.assertNotIn(TRACE_HEADER_KEY, headers)
249245

250246
@mock_sqs
251247
def test_double_patch(self):
@@ -306,20 +302,42 @@ def check_headers(**kwargs):
306302
"EC2", "DescribeInstances", request_id=request_id
307303
)
308304

309-
self.assertIn(MockTextMapPropagator.TRACE_ID_KEY, headers)
310-
self.assertEqual(
311-
str(span.get_span_context().trace_id),
312-
headers[MockTextMapPropagator.TRACE_ID_KEY],
305+
# only x-ray propagation is used in HTTP requests
306+
self.assertIn(TRACE_HEADER_KEY, headers)
307+
xray_context = headers[TRACE_HEADER_KEY]
308+
formated_trace_id = format_trace_id(
309+
span.get_span_context().trace_id
313310
)
314-
self.assertIn(MockTextMapPropagator.SPAN_ID_KEY, headers)
315-
self.assertEqual(
316-
str(span.get_span_context().span_id),
317-
headers[MockTextMapPropagator.SPAN_ID_KEY],
311+
formated_trace_id = (
312+
formated_trace_id[:8] + "-" + formated_trace_id[8:]
318313
)
319314

315+
self.assertEqual(
316+
xray_context.lower(),
317+
f"root=1-{formated_trace_id};parent={format_span_id(span.get_span_context().span_id)};sampled=1".lower(),
318+
)
320319
finally:
321320
set_global_textmap(previous_propagator)
322321

322+
@mock_ec2
323+
def test_override_xray_propagator_injects_into_request(self):
324+
headers = {}
325+
326+
def check_headers(**kwargs):
327+
nonlocal headers
328+
headers = kwargs["request"].headers
329+
330+
BotocoreInstrumentor().instrument()
331+
332+
ec2 = self._make_client("ec2")
333+
ec2.meta.events.register_first(
334+
"before-send.ec2.DescribeInstances", check_headers
335+
)
336+
ec2.describe_instances()
337+
338+
self.assertNotIn(MockTextMapPropagator.TRACE_ID_KEY, headers)
339+
self.assertNotIn(MockTextMapPropagator.SPAN_ID_KEY, headers)
340+
323341
@mock_xray
324342
def test_suppress_instrumentation_xray_client(self):
325343
xray_client = self._make_client("xray")

0 commit comments

Comments
 (0)