Skip to content

Commit 25ed17a

Browse files
authored
Support stable http sem conv for dependency type telemetry (Azure#39441)
1 parent c5d3701 commit 25ed17a

File tree

4 files changed

+99
-40
lines changed

4 files changed

+99
-40
lines changed

sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
([#39379](https://github.com/Azure/azure-sdk-for-python/pull/39379))
99
- Support stable http semantic conventions for breeze exporter - REQUESTS
1010
([#39208](https://github.com/Azure/azure-sdk-for-python/pull/39208))
11+
- Support stable http semantic conventions for breeze exporter - DEPENDENCIES
12+
([#39441](https://github.com/Azure/azure-sdk-for-python/pull/39441))
1113

1214
### Breaking Changes
1315

sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_exporter.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -336,11 +336,12 @@ def _convert_span_to_envelope(span: ReadableSpan) -> TelemetryItem:
336336
# https://github.com/Azure/azure-sdk-for-python/issues/9256
337337
data.type = span.attributes[_AZURE_SDK_NAMESPACE_NAME]
338338
data.target = trace_utils._get_azure_sdk_target_source(span.attributes)
339-
elif SpanAttributes.HTTP_METHOD in span.attributes: # HTTP
339+
elif HTTP_REQUEST_METHOD in span.attributes or SpanAttributes.HTTP_METHOD in span.attributes: # HTTP
340340
data.type = "HTTP"
341-
if SpanAttributes.HTTP_USER_AGENT in span.attributes:
341+
user_agent = trace_utils._get_user_agent(span.attributes)
342+
if user_agent:
342343
# TODO: Not exposed in Swagger, need to update def
343-
envelope.tags["ai.user.userAgent"] = span.attributes[SpanAttributes.HTTP_USER_AGENT]
344+
envelope.tags["ai.user.userAgent"] = user_agent
344345
url = trace_utils._get_url_for_http_dependency(span.attributes)
345346
# data
346347
if url:
@@ -353,10 +354,12 @@ def _convert_span_to_envelope(span: ReadableSpan) -> TelemetryItem:
353354
# http specific logic for name
354355
if path:
355356
data.name = "{} {}".format(
356-
span.attributes[SpanAttributes.HTTP_METHOD],
357+
span.attributes.get(HTTP_REQUEST_METHOD) or \
358+
span.attributes.get(SpanAttributes.HTTP_METHOD),
357359
path,
358360
)
359-
status_code = span.attributes.get(SpanAttributes.HTTP_STATUS_CODE)
361+
status_code = span.attributes.get(HTTP_RESPONSE_STATUS_CODE) or \
362+
span.attributes.get(SpanAttributes.HTTP_STATUS_CODE)
360363
if status_code:
361364
try:
362365
status_code = int(status_code) # type: ignore

sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_utils.py

Lines changed: 48 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ def _get_default_port_db(db_system: str) -> int:
4040
return 0
4141

4242

43-
def _get_default_port_http(scheme: Optional[str]) -> int:
43+
def _get_default_port_http(attributes: Attributes) -> int:
44+
scheme = _get_http_scheme(attributes)
4445
if scheme == "http":
4546
return 80
4647
if scheme == "https":
@@ -77,7 +78,8 @@ def _get_azure_sdk_target_source(attributes: Attributes) -> Optional[str]:
7778

7879
def _get_http_scheme(attributes: Attributes) -> Optional[str]:
7980
if attributes:
80-
scheme = attributes.get(SpanAttributes.HTTP_SCHEME)
81+
scheme = attributes.get(url_attributes.URL_SCHEME) or \
82+
attributes.get(SpanAttributes.HTTP_SCHEME)
8183
if scheme:
8284
return str(scheme)
8385
return None
@@ -87,14 +89,17 @@ def _get_http_scheme(attributes: Attributes) -> Optional[str]:
8789

8890

8991
@no_type_check
90-
def _get_url_for_http_dependency(attributes: Attributes, scheme: Optional[str] = None) -> Optional[str]:
91-
url = None
92+
def _get_url_for_http_dependency(attributes: Attributes) -> Optional[str]:
93+
url = ""
9294
if attributes:
93-
if not scheme:
94-
scheme = _get_http_scheme(attributes)
95+
# Stable sem conv only supports populating url from `url.full`
96+
if url_attributes.URL_FULL in attributes:
97+
return attributes[url_attributes.URL_FULL]
9598
if SpanAttributes.HTTP_URL in attributes:
96-
url = attributes[SpanAttributes.HTTP_URL]
97-
elif scheme and SpanAttributes.HTTP_TARGET in attributes:
99+
return attributes[SpanAttributes.HTTP_URL]
100+
# Scheme
101+
scheme = _get_http_scheme(attributes)
102+
if scheme and SpanAttributes.HTTP_TARGET in attributes:
98103
http_target = attributes[SpanAttributes.HTTP_TARGET]
99104
if SpanAttributes.HTTP_HOST in attributes:
100105
url = "{}://{}{}".format(
@@ -138,52 +143,60 @@ def _get_target_for_dependency_from_peer(attributes: Attributes) -> Optional[str
138143
port = attributes[SpanAttributes.NET_PEER_PORT]
139144
# TODO: check default port for rpc
140145
# This logic assumes default ports never conflict across dependency types
141-
if port != _get_default_port_http(
142-
str(attributes.get(SpanAttributes.HTTP_SCHEME))
143-
) and port != _get_default_port_db(str(attributes.get(SpanAttributes.DB_SYSTEM))):
146+
if port != _get_default_port_http(attributes) and \
147+
port != _get_default_port_db(str(attributes.get(SpanAttributes.DB_SYSTEM))):
144148
target = "{}:{}".format(target, port)
145149
return target
146150

147151

148152
@no_type_check
149153
def _get_target_and_path_for_http_dependency(
150154
attributes: Attributes,
151-
target: Optional[str],
152-
url: Optional[str],
153-
scheme: Optional[str] = None,
155+
target: Optional[str] = "",
156+
url: Optional[str] = "", # Usually populated by _get_url_for_http_dependency()
154157
) -> Tuple[Optional[str], str]:
155-
target_from_url = None
156-
path = ""
158+
parsed_url = None
159+
path = "/"
160+
default_port = _get_default_port_http(attributes)
161+
# Find path from url
162+
try:
163+
parsed_url = urlparse(url)
164+
if parsed_url.path:
165+
path = parsed_url.path
166+
except Exception: # pylint: disable=broad-except
167+
pass
168+
# Derive target
157169
if attributes:
158-
if not scheme:
159-
scheme = _get_http_scheme(attributes)
160-
if url:
161-
try:
162-
parse_url = urlparse(url)
163-
path = parse_url.path
164-
if not path:
165-
path = "/"
166-
if parse_url.port and parse_url.port == _get_default_port_http(scheme):
167-
target_from_url = parse_url.hostname
168-
else:
169-
target_from_url = parse_url.netloc
170-
except Exception: # pylint: disable=broad-except
171-
pass
172-
if SpanAttributes.PEER_SERVICE not in attributes:
170+
# Target from server.*
171+
if server_attributes.SERVER_ADDRESS in attributes:
172+
target = attributes[server_attributes.SERVER_ADDRESS]
173+
server_port = attributes.get(server_attributes.SERVER_PORT)
174+
# if not default port, include port in target
175+
if server_port != default_port:
176+
target = "{}:{}".format(target, server_port)
177+
# We only use these values for target if not already populated by peer.service
178+
elif not SpanAttributes.PEER_SERVICE in attributes:
179+
# Target from http.host
173180
if SpanAttributes.HTTP_HOST in attributes:
174181
host = attributes[SpanAttributes.HTTP_HOST]
175182
try:
176183
# urlparse insists on absolute URLs starting with "//"
177184
# This logic assumes host does not include a "//"
178185
host_name = urlparse("//" + str(host))
179-
if host_name.port == _get_default_port_http(scheme):
186+
# Ignore port from target if default port
187+
if host_name.port == default_port:
180188
target = host_name.hostname
181189
else:
190+
# Else include the whole host as the target
182191
target = str(host)
183192
except Exception: # pylint: disable=broad-except
184193
pass
185-
elif target_from_url and not target:
186-
target = target_from_url
194+
elif parsed_url:
195+
# Target from httpUrl
196+
if parsed_url.port and parsed_url.port == default_port:
197+
target = parsed_url.hostname
198+
else:
199+
target = parsed_url.netloc
187200
return (target, path)
188201

189202

@@ -250,7 +263,7 @@ def _get_url_for_http_request(attributes: Attributes) -> Optional[str]:
250263
if SpanAttributes.HTTP_URL in attributes:
251264
return attributes[SpanAttributes.HTTP_URL]
252265
# Scheme
253-
scheme = attributes.get(url_attributes.URL_SCHEME) or attributes.get(SpanAttributes.HTTP_SCHEME)
266+
scheme = _get_http_scheme(attributes)
254267
# Target
255268
http_target = ""
256269
if url_attributes.URL_PATH in attributes:

sdk/monitor/azure-monitor-opentelemetry-exporter/tests/trace/test_trace.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ def test_span_to_envelope_client_http(self):
362362
self.assertEqual(envelope.data.base_data.id, "a6f5d48acb4d31d9")
363363
self.assertEqual(envelope.data.base_data.duration, "0.00:00:01.001")
364364
self.assertTrue(envelope.data.base_data.success)
365+
self.assertEqual(envelope.data.base_data.data, "https://www.wikipedia.org/wiki/Rabbit")
365366

366367
self.assertEqual(envelope.data.base_type, "RemoteDependencyData")
367368
self.assertEqual(envelope.data.base_data.type, "HTTP")
@@ -418,6 +419,23 @@ def test_span_to_envelope_client_http(self):
418419
envelope = exporter._span_to_envelope(span)
419420
self.assertEqual(envelope.data.base_data.target, "www.wikipedia.org")
420421

422+
# Stable semconv
423+
span._attributes = {
424+
"http.request.method": "GET",
425+
"server.address": "www.example.com",
426+
"server.port": "1234",
427+
}
428+
envelope = exporter._span_to_envelope(span)
429+
self.assertEqual(envelope.data.base_data.target, "www.example.com:1234")
430+
span._attributes = {
431+
"http.request.method": "GET",
432+
"server.address": "www.example.com",
433+
"server.port": 80,
434+
"url.scheme": "http",
435+
}
436+
envelope = exporter._span_to_envelope(span)
437+
self.assertEqual(envelope.data.base_data.target, "www.example.com")
438+
421439
# url
422440
# spell-checker:ignore ddds
423441
span._attributes = {
@@ -449,6 +467,14 @@ def test_span_to_envelope_client_http(self):
449467
envelope = exporter._span_to_envelope(span)
450468
self.assertEqual(envelope.data.base_data.data, "https://192.168.0.1:8080/path/12314/?q=ddds#123")
451469

470+
# Stable semconv
471+
span._attributes = {
472+
"http.request.method": "GET",
473+
"url.full": "https://www.wikipedia.org/path/12314/?q=ddds#124",
474+
}
475+
envelope = exporter._span_to_envelope(span)
476+
self.assertEqual(envelope.data.base_data.data, "https://www.wikipedia.org/path/12314/?q=ddds#124")
477+
452478
# result_code
453479
span._attributes = {
454480
"http.method": "GET",
@@ -477,6 +503,21 @@ def test_span_to_envelope_client_http(self):
477503
envelope = exporter._span_to_envelope(span)
478504
self.assertEqual(envelope.data.base_data.result_code, "0")
479505

506+
# Stable semconv
507+
span._attributes = {
508+
"http.request.method": "GET",
509+
"http.response.status_code": "200",
510+
}
511+
envelope = exporter._span_to_envelope(span)
512+
self.assertEqual(envelope.data.base_data.result_code, "200")
513+
514+
span._attributes = {
515+
"http.request.method": "GET",
516+
"http.response.status_code": "",
517+
}
518+
envelope = exporter._span_to_envelope(span)
519+
self.assertEqual(envelope.data.base_data.result_code, "0")
520+
480521
def test_span_to_envelope_client_db(self):
481522
exporter = self._exporter
482523
start_time = 1575494316027613500

0 commit comments

Comments
 (0)