Skip to content

Commit 23163bc

Browse files
authored
[Core] Fix traceparent header in DistributedTracingPolicy (#40074)
When we get the trace context headers, we should first ensure that we are inside the HTTP span's context to ensure that the traceparent header contains the correct span ID. Signed-off-by: Paul Van Eck <[email protected]> * Fix mypy Signed-off-by: Paul Van Eck <[email protected]> --------- Signed-off-by: Paul Van Eck <[email protected]>
1 parent 1541021 commit 23163bc

File tree

5 files changed

+34
-17
lines changed

5 files changed

+34
-17
lines changed

sdk/core/azure-core/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929

3030
### Bugs Fixed
3131

32+
- Fixed an issue where the `traceparent` header was not being set correctly in the `DistributedTracingPolicy`. The `traceparent` header will now set based on the context of the HTTP client span. #40074
33+
3234
### Other Changes
3335

3436
- Added `opentelemetry-api` as an optional dependency for tracing. This can be installed with `pip install azure-core[tracing]`. #39563

sdk/core/azure-core/azure/core/pipeline/policies/_distributed_tracing.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from azure.core.rest import HttpResponse, HttpRequest
4040
from azure.core.settings import settings
4141
from azure.core.tracing import SpanKind
42+
from azure.core.tracing.common import change_context
4243
from azure.core.instrumentation import get_tracer
4344
from azure.core.tracing._models import TracingOptions
4445

@@ -127,8 +128,9 @@ def on_request(self, request: PipelineRequest[HTTPRequestType]) -> None:
127128
for attr, value in span_attributes.items():
128129
span.add_attribute(attr, value) # type: ignore
129130

130-
headers = span.to_header()
131-
request.http_request.headers.update(headers)
131+
with change_context(span.span_instance):
132+
headers = span.to_header()
133+
request.http_request.headers.update(headers)
132134
request.context[self.TRACING_CONTEXT] = span
133135
else:
134136
# Otherwise, use the core tracing.
@@ -151,10 +153,11 @@ def on_request(self, request: PipelineRequest[HTTPRequestType]) -> None:
151153
attributes=span_attributes,
152154
)
153155

154-
trace_context_headers = tracer.get_trace_context()
155-
request.http_request.headers.update(trace_context_headers)
156-
request.context[self.TRACING_CONTEXT] = otel_span
156+
with tracer.use_span(otel_span, end_on_exit=False):
157+
trace_context_headers = tracer.get_trace_context()
158+
request.http_request.headers.update(trace_context_headers)
157159

160+
request.context[self.TRACING_CONTEXT] = otel_span
158161
token = tracer._suppress_auto_http_instrumentation() # pylint: disable=protected-access
159162
request.context[self._SUPPRESSION_TOKEN] = token
160163

sdk/core/azure-core/azure/core/tracing/opentelemetry.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# ------------------------------------
55
from __future__ import annotations
66
from contextlib import contextmanager
7+
from contextvars import Token
78
from typing import Optional, Dict, Sequence, cast, Callable, Iterator, TYPE_CHECKING
89

910
from opentelemetry import context as otel_context_module, trace
@@ -221,23 +222,23 @@ def get_trace_context(cls) -> Dict[str, str]:
221222
return trace_context
222223

223224
@classmethod
224-
def _suppress_auto_http_instrumentation(cls) -> object:
225+
def _suppress_auto_http_instrumentation(cls) -> Token:
225226
"""Enabled automatic HTTP instrumentation suppression.
226227
227228
Since azure-core already instruments HTTP calls, we need to suppress any automatic HTTP
228229
instrumentation provided by other libraries to prevent duplicate spans. This has no effect if no
229230
automatic HTTP instrumentation libraries are being used.
230231
231232
:return: A token that can be used to detach the suppression key from the context
232-
:rtype: object
233+
:rtype: ~contextvars.Token
233234
"""
234235
return otel_context_module.attach(otel_context_module.set_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY, True))
235236

236237
@classmethod
237-
def _detach_from_context(cls, token: object) -> None:
238+
def _detach_from_context(cls, token: Token) -> None:
238239
"""Detach a token from the context.
239240
240241
:param token: The token to detach
241-
:type token: object
242+
:type token: ~contextvars.Token
242243
"""
243244
otel_context_module.detach(token)

sdk/core/azure-core/tests/test_tracing_policy.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from azure.core.tracing._models import SpanKind
1616
from azure.core.tracing._abstract_span import HttpSpanMixin
1717
import pytest
18+
from opentelemetry.trace import format_span_id, format_trace_id
1819
from opentelemetry.instrumentation.requests import RequestsInstrumentor
1920

2021
from utils import HTTP_RESPONSES, HTTP_REQUESTS, create_http_response, request_and_responses_product
@@ -40,7 +41,7 @@ def test_distributed_tracing_policy_solo(self, tracing_implementation, http_requ
4041
response.status_code = 202
4142
response.headers[policy._RESPONSE_ID] = "some request id"
4243

43-
assert request.headers.get("traceparent") == "123456789"
44+
assert request.headers.get("traceparent") == "00-12345-GET-01"
4445

4546
policy.on_response(pipeline_request, PipelineResponse(request, response, PipelineContext(None)))
4647
time.sleep(0.001)
@@ -197,7 +198,7 @@ def test_distributed_tracing_policy_with_user_agent(self, tracing_implementation
197198
response.headers[policy._RESPONSE_ID] = "some request id"
198199
pipeline_response = PipelineResponse(request, response, PipelineContext(None))
199200

200-
assert request.headers.get("traceparent") == "123456789"
201+
assert request.headers.get("traceparent") == "00-12345-GET-01"
201202

202203
policy.on_response(pipeline_request, pipeline_response)
203204

@@ -381,6 +382,10 @@ def test_distributed_tracing_policy(self, tracing_helper, http_request, http_res
381382
assert finished_spans[0].name == "GET"
382383
assert finished_spans[0].parent is root_span.get_span_context()
383384

385+
span_context = finished_spans[0].get_span_context()
386+
assert traceparent.split("-")[1] == format_trace_id(span_context.trace_id)
387+
assert traceparent.split("-")[2] == format_span_id(span_context.span_id)
388+
384389
assert finished_spans[0].attributes.get(policy._HTTP_REQUEST_METHOD) == "GET"
385390
assert finished_spans[0].attributes.get(policy._URL_FULL) == "http://localhost/temp?query=query"
386391
assert finished_spans[0].attributes.get(policy._SERVER_ADDRESS) == "localhost"
@@ -498,6 +503,10 @@ def test_distributed_tracing_policy_with_user_agent_policy(self, tracing_helper,
498503
assert finished_spans[0].name == "GET"
499504
assert finished_spans[0].parent is root_span.get_span_context()
500505

506+
span_context = finished_spans[0].get_span_context()
507+
assert traceparent.split("-")[1] == format_trace_id(span_context.trace_id)
508+
assert traceparent.split("-")[2] == format_span_id(span_context.span_id)
509+
501510
assert finished_spans[0].attributes.get(policy._HTTP_REQUEST_METHOD) == "GET"
502511
assert finished_spans[0].attributes.get(policy._URL_FULL) == "http://localhost/temp?query=query"
503512
assert finished_spans[0].attributes.get(policy._SERVER_ADDRESS) == "localhost"

sdk/core/azure-core/tests/tracing_common.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,12 @@ def __init__(self, span=None, name="span", kind=None):
3535
:param name: The name of the OpenTelemetry span to create if a new span is needed
3636
:type name: str
3737
"""
38-
self._span = span
3938
self.name = name
4039
self._kind = kind or SpanKind.UNSPECIFIED
4140
self.attributes = {}
4241
self.children = []
4342
if self.CONTEXT:
4443
self.CONTEXT[-1].children.append(self)
45-
self.CONTEXT.append(self)
4644
self.status = None
4745

4846
def __str__(self):
@@ -58,7 +56,7 @@ def span_instance(self):
5856
"""
5957
:return: The OpenTelemetry span that is being wrapped.
6058
"""
61-
return self._span
59+
return self
6260

6361
def span(self, name="span"):
6462
# type: (Optional[str]) -> OpenCensusSpan
@@ -84,13 +82,15 @@ def kind(self, value):
8482

8583
def __enter__(self):
8684
"""Start a span."""
85+
self.CONTEXT.append(self)
8786
return self
8887

8988
def __exit__(self, exception_type, exception_value, traceback):
9089
"""Finish a span."""
9190
if exception_value:
9291
self.status = exception_value.args[0]
93-
self.CONTEXT.pop()
92+
if self.CONTEXT and self.CONTEXT[-1] == self:
93+
self.CONTEXT.pop()
9494

9595
def start(self):
9696
# type: () -> None
@@ -100,15 +100,17 @@ def start(self):
100100
def finish(self):
101101
# type: () -> None
102102
"""Set the end time for a span."""
103-
self.CONTEXT.pop()
103+
if self.CONTEXT and self.CONTEXT[-1] == self:
104+
self.CONTEXT.pop()
104105

105106
def to_header(self):
106107
# type: () -> Dict[str, str]
107108
"""
108109
Returns a dictionary with the header labels and values.
109110
:return: A key value pair dictionary
110111
"""
111-
return {"traceparent": "123456789"}
112+
current_span = self.get_current_span()
113+
return {"traceparent": f"00-12345-{current_span.name}-01"}
112114

113115
def add_attribute(self, key, value):
114116
# type: (str, Union[str, int]) -> None

0 commit comments

Comments
 (0)