Skip to content

Commit acfb396

Browse files
authored
[Tracing] Add error.type attribute to spans (#34619)
If an exception is raised, the fully-qualified name of the exception is now stored in the error.type span attribute. Signed-off-by: Paul Van Eck <[email protected]>
1 parent 9e5c2e0 commit acfb396

File tree

6 files changed

+55
-0
lines changed

6 files changed

+55
-0
lines changed

sdk/core/azure-core-tracing-opentelemetry/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+
- If a span exits with an exception, the exception name is now recorded in the `error.type` attribute. ([#34619](https://github.com/Azure/azure-sdk-for-python/pull/34619))
8+
79
### Breaking Changes
810

911
- Remapped certain attributes to converge with OpenTelemetry semantic conventions version `1.23.1` ([#34089](https://github.com/Azure/azure-sdk-for-python/pull/34089)):

sdk/core/azure-core-tracing-opentelemetry/azure/core/tracing/ext/opentelemetry_span/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545

4646
_SUPPRESSED_SPAN_FLAG = "SUPPRESSED_SPAN_FLAG"
4747
_LAST_UNSUPPRESSED_SPAN = "LAST_UNSUPPRESSED_SPAN"
48+
_ERROR_SPAN_ATTRIBUTE = "error.type"
4849

4950

5051
class OpenTelemetrySpan(HttpSpanMixin, object):
@@ -226,6 +227,10 @@ def __enter__(self) -> "OpenTelemetrySpan":
226227

227228
def __exit__(self, exception_type, exception_value, traceback) -> None:
228229
# Finish the span.
230+
if exception_type:
231+
module = exception_type.__module__ if exception_type.__module__ != "builtins" else ""
232+
error_type = f"{module}.{exception_type.__qualname__}" if module else exception_type.__qualname__
233+
self.add_attribute(_ERROR_SPAN_ATTRIBUTE, error_type)
229234
if self._current_ctxt_manager:
230235
self._current_ctxt_manager.__exit__(exception_type, exception_value, traceback) # pylint: disable=no-member
231236
self._current_ctxt_manager = None

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import requests
1313

1414

15+
from azure.core.exceptions import ClientAuthenticationError
1516
from azure.core.tracing.ext.opentelemetry_span import OpenTelemetrySpan
1617
from azure.core.tracing import SpanKind, AbstractSpan
1718
from azure.core import __version__ as core_version
@@ -369,3 +370,20 @@ def test_span_kind(self, tracing_helper):
369370

370371
with pytest.raises(ValueError):
371372
wrapped_class.kind = "somethingstuid"
373+
374+
def test_error_type_attribute_builtin_error(self, tracing_helper):
375+
with tracing_helper.tracer.start_as_current_span("Root") as parent:
376+
with pytest.raises(ValueError):
377+
with OpenTelemetrySpan() as wrapped_class:
378+
raise ValueError("This is a test error")
379+
assert wrapped_class.span_instance.attributes.get("error.type") == "ValueError"
380+
381+
def test_error_type_attribute_azure_error(self, tracing_helper):
382+
with tracing_helper.tracer.start_as_current_span("Root") as parent:
383+
with pytest.raises(ClientAuthenticationError):
384+
with OpenTelemetrySpan() as wrapped_class:
385+
raise ClientAuthenticationError("This is a test error")
386+
assert (
387+
wrapped_class.span_instance.attributes.get("error.type")
388+
== "azure.core.exceptions.ClientAuthenticationError"
389+
)

sdk/core/azure-core/CHANGELOG.md

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

1111
### Other Changes
1212

13+
- HTTP tracing spans will now include an `error.type` attribute if an error status code is returned. #34619
14+
1315
## 1.30.1 (2024-02-29)
1416

1517
### Other Changes

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ class HttpSpanMixin:
260260
_HTTP_STATUS_CODE = "http.status_code"
261261
_NET_PEER_NAME = "net.peer.name"
262262
_NET_PEER_PORT = "net.peer.port"
263+
_ERROR_TYPE = "error.type"
263264

264265
def set_http_attributes(
265266
self: AbstractSpan, request: HttpRequestType, response: Optional[HttpResponseType] = None
@@ -289,8 +290,11 @@ def set_http_attributes(
289290
self.add_attribute(HttpSpanMixin._HTTP_USER_AGENT, user_agent)
290291
if response and response.status_code:
291292
self.add_attribute(HttpSpanMixin._HTTP_STATUS_CODE, response.status_code)
293+
if response.status_code >= 400:
294+
self.add_attribute(HttpSpanMixin._ERROR_TYPE, str(response.status_code))
292295
else:
293296
self.add_attribute(HttpSpanMixin._HTTP_STATUS_CODE, 504)
297+
self.add_attribute(HttpSpanMixin._ERROR_TYPE, "504")
294298

295299

296300
class Link:

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ def test_distributed_tracing_policy_solo(http_request, http_response):
5757
assert network_span.attributes.get("x-ms-request-id") == "some request id"
5858
assert network_span.attributes.get("x-ms-client-request-id") == "some client request id"
5959
assert network_span.attributes.get("http.status_code") == 202
60+
assert "error.type" not in network_span.attributes
6061

6162
# Check on_exception
6263
network_span = root_span.children[1]
@@ -68,6 +69,29 @@ def test_distributed_tracing_policy_solo(http_request, http_response):
6869
assert network_span.attributes.get("http.user_agent") is None
6970
assert network_span.attributes.get("x-ms-request-id") == None
7071
assert network_span.attributes.get("http.status_code") == 504
72+
assert network_span.attributes.get("error.type")
73+
74+
75+
@pytest.mark.parametrize("http_request,http_response", request_and_responses_product(HTTP_RESPONSES))
76+
def test_distributed_tracing_policy_error_response(http_request, http_response):
77+
"""Test policy when the HTTP response corresponds to an error."""
78+
settings.tracing_implementation.set_value(FakeSpan)
79+
with FakeSpan(name="parent") as root_span:
80+
policy = DistributedTracingPolicy(tracing_attributes={"myattr": "myvalue"})
81+
82+
request = http_request("GET", "http://localhost/temp?query=query")
83+
84+
pipeline_request = PipelineRequest(request, PipelineContext(None))
85+
policy.on_request(pipeline_request)
86+
87+
response = create_http_response(http_response, request, None)
88+
response.headers = request.headers
89+
response.status_code = 403
90+
91+
policy.on_response(pipeline_request, PipelineResponse(request, response, PipelineContext(None)))
92+
network_span = root_span.children[0]
93+
assert network_span.name == "/temp"
94+
assert network_span.attributes.get("error.type") == "403"
7195

7296

7397
@pytest.mark.parametrize("http_request,http_response", request_and_responses_product(HTTP_RESPONSES))

0 commit comments

Comments
 (0)