Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 005bac2

Browse files
committedMar 31, 2023
botocore: always use x-ray for http header injection
AWS will only propagate the x-ray header's context through to other services. Injecting context to any other header with a different propagator will result in the context being dropped. The propagator is overridable by the user as an argument to BotocoreInstrumentor to give them final control.
1 parent 20d2cc3 commit 005bac2

File tree

4 files changed

+77
-34
lines changed

4 files changed

+77
-34
lines changed
 

‎CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
- Make Django request span attributes available for `start_span`.
1515
([#1730](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1730))
1616

17+
### Changed
18+
19+
- `opentelemetry-instrumentation-botocore` now uses the AWS X-Ray propagator by
20+
default
21+
([#1741](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1741))
1722

1823
## Version 1.17.0/0.38b0 (2023-03-22)
1924

‎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.39b0.dev",
3030
"opentelemetry-semantic-conventions == 0.39b0.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

+22-10
Original file line numberDiff line numberDiff line change
@@ -101,22 +101,16 @@ 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 (
105+
AwsXRayPropagator,
106+
)
105107
from opentelemetry.semconv.trace import SpanAttributes
106108
from opentelemetry.trace import get_tracer
107109
from opentelemetry.trace.span import Span
108110

109111
logger = logging.getLogger(__name__)
110112

111113

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-
120114
class BotocoreInstrumentor(BaseInstrumentor):
121115
"""An instrumentor for Botocore.
122116
@@ -127,6 +121,7 @@ def __init__(self):
127121
super().__init__()
128122
self.request_hook = None
129123
self.response_hook = None
124+
self.propagator = AwsXRayPropagator()
130125

131126
def instrumentation_dependencies(self) -> Collection[str]:
132127
return _instruments
@@ -140,6 +135,10 @@ def _instrument(self, **kwargs):
140135
self.request_hook = kwargs.get("request_hook")
141136
self.response_hook = kwargs.get("response_hook")
142137

138+
propagator = kwargs.get("propagator")
139+
if propagator != None:
140+
self.propagator = propagator
141+
143142
wrap_function_wrapper(
144143
"botocore.client",
145144
"BaseClient._make_api_call",
@@ -149,13 +148,26 @@ def _instrument(self, **kwargs):
149148
wrap_function_wrapper(
150149
"botocore.endpoint",
151150
"Endpoint.prepare_request",
152-
_patched_endpoint_prepare_request,
151+
self._patched_endpoint_prepare_request,
153152
)
154153

155154
def _uninstrument(self, **kwargs):
156155
unwrap(BaseClient, "_make_api_call")
157156
unwrap(Endpoint, "prepare_request")
158157

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

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

+49-24
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@
3939
from opentelemetry.semconv.trace import SpanAttributes
4040
from opentelemetry.test.mock_textmap import MockTextMapPropagator
4141
from opentelemetry.test.test_base import TestBase
42+
from opentelemetry.trace.span import Span, format_span_id, format_trace_id
43+
from opentelemetry.propagators.aws.aws_xray_propagator import (
44+
TRACE_HEADER_KEY,
45+
)
4246

4347
_REQUEST_ID_REGEX_MATCH = r"[A-Z0-9]{52}"
4448

@@ -225,27 +229,21 @@ def test_unpatch(self):
225229
@mock_ec2
226230
def test_uninstrument_does_not_inject_headers(self):
227231
headers = {}
228-
previous_propagator = get_global_textmap()
229-
try:
230-
set_global_textmap(MockTextMapPropagator())
231232

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

235-
ec2 = self._make_client("ec2")
236+
ec2 = self._make_client("ec2")
236237

237-
BotocoreInstrumentor().uninstrument()
238+
BotocoreInstrumentor().uninstrument()
238239

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()
240+
ec2.meta.events.register_first(
241+
"before-send.ec2.DescribeInstances", intercept_headers
242+
)
243+
with self.tracer_provider.get_tracer("test").start_span("parent"):
244+
ec2.describe_instances()
244245

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

250248
@mock_sqs
251249
def test_double_patch(self):
@@ -306,20 +304,47 @@ def check_headers(**kwargs):
306304
"EC2", "DescribeInstances", request_id=request_id
307305
)
308306

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],
307+
# only x-ray propagation is used in HTTP requests
308+
self.assertIn(TRACE_HEADER_KEY, headers)
309+
xray_context = headers[TRACE_HEADER_KEY]
310+
formated_trace_id = format_trace_id(
311+
span.get_span_context().trace_id
313312
)
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],
313+
formated_trace_id = (
314+
formated_trace_id[:8] + "-" + formated_trace_id[8:]
318315
)
319316

317+
self.assertEqual(
318+
xray_context.lower(),
319+
f"root=1-{formated_trace_id};parent={format_span_id(span.get_span_context().span_id)};sampled=1".lower(),
320+
)
320321
finally:
321322
set_global_textmap(previous_propagator)
322323

324+
@mock_ec2
325+
def test_override_xray_propagator_injects_into_request(self):
326+
headers = {}
327+
328+
def check_headers(**kwargs):
329+
nonlocal headers
330+
headers = kwargs["request"].headers
331+
332+
BotocoreInstrumentor().instrument()
333+
334+
ec2 = self._make_client("ec2")
335+
ec2.meta.events.register_first(
336+
"before-send.ec2.DescribeInstances", check_headers
337+
)
338+
ec2.describe_instances()
339+
340+
request_id = "fdcdcab1-ae5c-489e-9c33-4637c5dda355"
341+
span = self.assert_span(
342+
"EC2", "DescribeInstances", request_id=request_id
343+
)
344+
345+
self.assertNotIn(MockTextMapPropagator.TRACE_ID_KEY, headers)
346+
self.assertNotIn(MockTextMapPropagator.SPAN_ID_KEY, headers)
347+
323348
@mock_xray
324349
def test_suppress_instrumentation_xray_client(self):
325350
xray_client = self._make_client("xray")

0 commit comments

Comments
 (0)
Please sign in to comment.