Skip to content

Commit 406d463

Browse files
authored
[Core] Add new attribute to resent HTTP request spans (#35069)
`DistributedTracingPolicy` will now set an attribute, `http.request.resend_count`, on HTTP spans for resent requests to indicate the resend attempt count. Signed-off-by: Paul Van Eck <[email protected]>
1 parent 440b38e commit 406d463

File tree

6 files changed

+106
-2
lines changed

6 files changed

+106
-2
lines changed

sdk/core/azure-core/CHANGELOG.md

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

55
### Features Added
66

7+
- Tracing: `DistributedTracingPolicy` will now set an attribute, `http.request.resend_count`, on HTTP spans for resent requests to indicate the resend attempt number. #35069
8+
79
### Breaking Changes
810

911
### Bugs Fixed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ class DistributedTracingPolicy(SansIOHTTPPolicy[HTTPRequestType, HTTPResponseTyp
7676
TRACING_CONTEXT = "TRACING_CONTEXT"
7777
_REQUEST_ID = "x-ms-client-request-id"
7878
_RESPONSE_ID = "x-ms-request-id"
79+
_HTTP_RESEND_COUNT = "http.request.resend_count"
7980

8081
def __init__(self, **kwargs: Any):
8182
self._network_span_namer = kwargs.get("network_span_namer", _default_network_span_namer)
@@ -125,6 +126,8 @@ def end_span(
125126
http_request: Union[HttpRequest, LegacyHttpRequest] = request.http_request
126127
if span is not None:
127128
span.set_http_attributes(http_request, response=response)
129+
if request.context.get("retry_count"):
130+
span.add_attribute(self._HTTP_RESEND_COUNT, request.context["retry_count"])
128131
request_id = http_request.headers.get(self._REQUEST_ID)
129132
if request_id is not None:
130133
span.add_attribute(self._REQUEST_ID, request_id)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,7 @@ def send(self, request: PipelineRequest[HTTPRequestType]) -> PipelineResponse[HT
528528
)
529529
try:
530530
self._configure_timeout(request, absolute_timeout, is_response_error)
531+
request.context["retry_count"] = len(retry_settings["history"])
531532
response = self.next.send(request)
532533
if self.is_retry(retry_settings, response):
533534
retry_active = self.increment(retry_settings, response=response)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ async def send(
176176
)
177177
try:
178178
self._configure_timeout(request, absolute_timeout, is_response_error)
179+
request.context["retry_count"] = len(retry_settings["history"])
179180
response = await self.next.send(request)
180181
if self.is_retry(retry_settings, response):
181182
retry_active = self.increment(retry_settings, response=response)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# ------------------------------------
2+
# Copyright (c) Microsoft Corporation.
3+
# Licensed under the MIT License.
4+
# ------------------------------------
5+
"""Tests for the distributed tracing policy in an async pipeline."""
6+
7+
import pytest
8+
9+
10+
from azure.core.pipeline import AsyncPipeline
11+
from azure.core.pipeline.policies import AsyncRetryPolicy, DistributedTracingPolicy
12+
from azure.core.pipeline.transport import (
13+
HttpResponse,
14+
AsyncHttpTransport,
15+
)
16+
from azure.core.settings import settings
17+
18+
from tracing_common import FakeSpan
19+
from utils import HTTP_REQUESTS
20+
21+
22+
@pytest.mark.asyncio
23+
@pytest.mark.parametrize("http_request", HTTP_REQUESTS)
24+
async def test_span_retry_attributes(http_request):
25+
class MockTransport(AsyncHttpTransport):
26+
def __init__(self):
27+
self._count = 0
28+
29+
async def __aexit__(self, exc_type, exc_val, exc_tb):
30+
pass
31+
32+
async def close(self):
33+
pass
34+
35+
async def open(self):
36+
pass
37+
38+
async def send(self, request, **kwargs):
39+
self._count += 1
40+
response = HttpResponse(request, None)
41+
response.status_code = 429
42+
return response
43+
44+
http_request = http_request("GET", "http://localhost/")
45+
retry_policy = AsyncRetryPolicy(retry_total=2)
46+
distributed_tracing_policy = DistributedTracingPolicy()
47+
transport = MockTransport()
48+
49+
settings.tracing_implementation.set_value(FakeSpan)
50+
with FakeSpan(name="parent") as root_span:
51+
pipeline = AsyncPipeline(transport, [retry_policy, distributed_tracing_policy])
52+
await pipeline.run(http_request)
53+
54+
assert transport._count == 3
55+
assert len(root_span.children) == 3
56+
assert root_span.children[0].attributes.get("http.request.resend_count") is None
57+
assert root_span.children[1].attributes.get("http.request.resend_count") == 1
58+
assert root_span.children[2].attributes.get("http.request.resend_count") == 2

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

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
"""Tests for the distributed tracing policy."""
66
import logging
77

8-
from azure.core.pipeline import PipelineResponse, PipelineRequest, PipelineContext
9-
from azure.core.pipeline.policies import DistributedTracingPolicy, UserAgentPolicy
8+
from azure.core.pipeline import Pipeline, PipelineResponse, PipelineRequest, PipelineContext
9+
from azure.core.pipeline.policies import DistributedTracingPolicy, UserAgentPolicy, RetryPolicy
10+
from azure.core.pipeline.transport import HttpTransport
1011
from azure.core.settings import settings
1112
from tracing_common import FakeSpan
1213
import time
@@ -210,6 +211,44 @@ def test_distributed_tracing_policy_with_user_agent(http_request, http_response)
210211
assert network_span.status == "Transport trouble"
211212

212213

214+
@pytest.mark.parametrize("http_request,http_response", request_and_responses_product(HTTP_RESPONSES))
215+
def test_span_retry_attributes(http_request, http_response):
216+
class MockTransport(HttpTransport):
217+
def __init__(self):
218+
self._count = 0
219+
220+
def __exit__(self, exc_type, exc_val, exc_tb):
221+
pass
222+
223+
def close(self):
224+
pass
225+
226+
def open(self):
227+
pass
228+
229+
def send(self, request, **kwargs):
230+
self._count += 1
231+
response = create_http_response(http_response, request, None)
232+
response.status_code = 429
233+
return response
234+
235+
settings.tracing_implementation.set_value(FakeSpan)
236+
237+
http_request = http_request("GET", "http://localhost/")
238+
retry_policy = RetryPolicy(retry_total=2)
239+
distributed_tracing_policy = DistributedTracingPolicy()
240+
transport = MockTransport()
241+
242+
with FakeSpan(name="parent") as root_span:
243+
pipeline = Pipeline(transport, [retry_policy, distributed_tracing_policy])
244+
pipeline.run(http_request)
245+
assert transport._count == 3
246+
assert len(root_span.children) == 3
247+
assert root_span.children[0].attributes.get("http.request.resend_count") is None
248+
assert root_span.children[1].attributes.get("http.request.resend_count") == 1
249+
assert root_span.children[2].attributes.get("http.request.resend_count") == 2
250+
251+
213252
@pytest.mark.parametrize("http_request,http_response", request_and_responses_product(HTTP_RESPONSES))
214253
def test_span_namer(http_request, http_response):
215254
settings.tracing_implementation.set_value(FakeSpan)

0 commit comments

Comments
 (0)