Skip to content

Commit a885c3e

Browse files
authored
Default missing/invalid status codes to "0" for standard metrics and trace payloads and fix success criteria (#36079)
1 parent c5da2d9 commit a885c3e

File tree

5 files changed

+86
-14
lines changed

5 files changed

+86
-14
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111

1212
### Bugs Fixed
1313

14+
- Default missing/invalid status codes to "0" for standard metrics/trace payloads, change
15+
success criteria to `False` for those invalid cases, change success criteria to status_code < 400 for
16+
both client and server standard metrics
17+
([#36079](https://github.com/Azure/azure-sdk-for-python/pull/36079))
18+
1419
### Other Changes
1520

1621
## 1.0.0b26 (2024-05-29)

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

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -229,11 +229,18 @@ def _handle_std_metric_envelope(
229229
attributes = {}
230230
# TODO: switch to semconv constants
231231
status_code = attributes.get("http.status_code")
232+
if status_code:
233+
try:
234+
status_code = int(status_code) # type: ignore
235+
except ValueError:
236+
status_code = 0
237+
else:
238+
status_code = 0
232239
if name == "http.client.duration":
233240
properties["_MS.MetricId"] = "dependencies/duration"
234241
properties["_MS.IsAutocollected"] = "True"
235242
properties["Dependency.Type"] = "http"
236-
properties["Dependency.Success"] = str(_is_status_code_success(status_code, 400)) # type: ignore
243+
properties["Dependency.Success"] = str(_is_status_code_success(status_code)) # type: ignore
237244
target = None
238245
if "peer.service" in attributes:
239246
target = attributes["peer.service"] # type: ignore
@@ -260,7 +267,7 @@ def _handle_std_metric_envelope(
260267
# TODO: operation/synthetic
261268
properties["cloud/roleInstance"] = tags["ai.cloud.roleInstance"] # type: ignore
262269
properties["cloud/roleName"] = tags["ai.cloud.role"] # type: ignore
263-
properties["Request.Success"] = str(_is_status_code_success(status_code, 500)) # type: ignore
270+
properties["Request.Success"] = str(_is_status_code_success(status_code)) # type: ignore
264271
else:
265272
# Any other autocollected metrics are not supported yet for standard metrics
266273
# We ignore these envelopes in these cases
@@ -273,11 +280,13 @@ def _handle_std_metric_envelope(
273280
return envelope
274281

275282

276-
def _is_status_code_success(status_code: Optional[str], threshold: int) -> bool:
277-
if status_code is None:
283+
def _is_status_code_success(status_code: Optional[str]) -> bool:
284+
if status_code is None or status_code == 0:
278285
return False
279286
try:
280-
return int(status_code) < threshold
287+
# Success criteria based solely off status code is True only if status_code < 400
288+
# for both client and server spans
289+
return int(status_code) < 400
281290
except ValueError:
282291
return False
283292

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

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -275,14 +275,17 @@ def _convert_span_to_envelope(span: ReadableSpan) -> TelemetryItem:
275275
)
276276
except Exception: # pylint: disable=broad-except
277277
pass
278-
if SpanAttributes.HTTP_STATUS_CODE in span.attributes:
279-
status_code = span.attributes[SpanAttributes.HTTP_STATUS_CODE]
280-
data.response_code = str(status_code)
278+
status_code = span.attributes.get(SpanAttributes.HTTP_STATUS_CODE)
279+
if status_code:
281280
try:
282281
status_code = int(status_code) # type: ignore
283282
except ValueError:
284283
status_code = 0
285-
data.success = span.status.is_ok and status_code not in range(400, 500)
284+
else:
285+
status_code = 0
286+
data.response_code = str(status_code)
287+
# Success criteria for server spans depends on span.success and the actual status code
288+
data.success = span.status.is_ok and status_code and status_code not in range(400, 500)
286289
elif SpanAttributes.MESSAGING_SYSTEM in span.attributes: # Messaging
287290
if SpanAttributes.NET_PEER_IP in span.attributes:
288291
envelope.tags[ContextTagKeys.AI_LOCATION_IP] = span.attributes[SpanAttributes.NET_PEER_IP]
@@ -320,7 +323,7 @@ def _convert_span_to_envelope(span: ReadableSpan) -> TelemetryItem:
320323
id="{:016x}".format(span.context.span_id),
321324
result_code="0",
322325
duration=_utils.ns_to_duration(time),
323-
success=span.status.is_ok,
326+
success=span.status.is_ok, # Success depends only on span status
324327
properties={},
325328
)
326329
envelope.data = MonitorBase(
@@ -423,9 +426,15 @@ def _convert_span_to_envelope(span: ReadableSpan) -> TelemetryItem:
423426
# data is url
424427
if url:
425428
data.data = url
426-
if SpanAttributes.HTTP_STATUS_CODE in span.attributes:
427-
status_code = span.attributes[SpanAttributes.HTTP_STATUS_CODE]
428-
data.result_code = str(status_code)
429+
status_code = span.attributes.get(SpanAttributes.HTTP_STATUS_CODE)
430+
if status_code:
431+
try:
432+
status_code = int(status_code) # type: ignore
433+
except ValueError:
434+
status_code = 0
435+
else:
436+
status_code = 0
437+
data.result_code = str(status_code)
429438
elif SpanAttributes.DB_SYSTEM in span.attributes: # Database
430439
db_system = span.attributes[SpanAttributes.DB_SYSTEM]
431440
if db_system == DbSystemValues.MYSQL.value:

sdk/monitor/azure-monitor-opentelemetry-exporter/tests/metrics/test_metrics.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,10 +347,12 @@ def test_point_to_envelope_std_metric_client_duration(self):
347347
point.attributes["http.status_code"] = None
348348
envelope = exporter._point_to_envelope(point, "http.client.duration", resource)
349349
self.assertEqual(envelope.data.base_data.properties['Dependency.Success'], "False")
350+
self.assertEqual(envelope.data.base_data.properties['dependency/resultCode'], "0")
350351

351352
point.attributes["http.status_code"] = "None"
352353
envelope = exporter._point_to_envelope(point, "http.client.duration", resource)
353354
self.assertEqual(envelope.data.base_data.properties['Dependency.Success'], "False")
355+
self.assertEqual(envelope.data.base_data.properties['dependency/resultCode'], "0")
354356

355357

356358
def test_point_to_envelope_std_metric_server_duration(self):
@@ -386,17 +388,19 @@ def test_point_to_envelope_std_metric_server_duration(self):
386388
self.assertEqual(envelope.data.base_data.metrics[0].value, 15.0)
387389

388390
# Success/Failure
389-
point.attributes["http.status_code"] = 600
391+
point.attributes["http.status_code"] = 500
390392
envelope = exporter._point_to_envelope(point, "http.server.duration", resource)
391393
self.assertEqual(envelope.data.base_data.properties['Request.Success'], "False")
392394

393395
point.attributes["http.status_code"] = None
394396
envelope = exporter._point_to_envelope(point, "http.server.duration", resource)
395397
self.assertEqual(envelope.data.base_data.properties['Request.Success'], "False")
398+
self.assertEqual(envelope.data.base_data.properties.get('request/resultCode'), "0")
396399

397400
point.attributes["http.status_code"] = "None"
398401
envelope = exporter._point_to_envelope(point, "http.server.duration", resource)
399402
self.assertEqual(envelope.data.base_data.properties['Request.Success'], "False")
403+
self.assertEqual(envelope.data.base_data.properties.get('request/resultCode'), "0")
400404

401405

402406
def test_point_to_envelope_std_metric_unsupported(self):

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

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,34 @@ def test_span_to_envelope_client_http(self):
346346
envelope = exporter._span_to_envelope(span)
347347
self.assertEqual(envelope.data.base_data.data, "https://192.168.0.1:8080/path/12314/?q=ddds#123")
348348

349+
# result_code
350+
span._attributes = {
351+
"http.method": "GET",
352+
"http.scheme": "https",
353+
"http.url": "https://www.example.com",
354+
"http.status_code": None
355+
}
356+
envelope = exporter._span_to_envelope(span)
357+
envelope = exporter._span_to_envelope(span)
358+
self.assertEqual(envelope.data.base_data.result_code, "0")
359+
360+
span._attributes = {
361+
"http.method": "GET",
362+
"http.scheme": "https",
363+
"http.url": "https://www.example.com",
364+
}
365+
envelope = exporter._span_to_envelope(span)
366+
self.assertEqual(envelope.data.base_data.result_code, "0")
367+
368+
span._attributes = {
369+
"http.method": "GET",
370+
"http.scheme": "https",
371+
"http.url": "https://www.example.com",
372+
"http.status_code": "",
373+
}
374+
envelope = exporter._span_to_envelope(span)
375+
self.assertEqual(envelope.data.base_data.result_code, "0")
376+
349377
def test_span_to_envelope_client_db(self):
350378
exporter = self._exporter
351379
start_time = 1575494316027613500
@@ -857,7 +885,24 @@ def test_span_envelope_server_http(self):
857885
}
858886
envelope = exporter._span_to_envelope(span)
859887
self.assertFalse(envelope.data.base_data.success)
888+
self.assertEqual(envelope.data.base_data.response_code, "400")
889+
890+
span._attributes = {
891+
"http.method": "GET",
892+
"net.peer.ip": "peer_ip",
893+
}
894+
envelope = exporter._span_to_envelope(span)
895+
self.assertFalse(envelope.data.base_data.success)
896+
self.assertEqual(envelope.data.base_data.response_code, "0")
860897

898+
span._attributes = {
899+
"http.method": "GET",
900+
"net.peer.ip": "peer_ip",
901+
"http.status_code": "",
902+
}
903+
envelope = exporter._span_to_envelope(span)
904+
self.assertFalse(envelope.data.base_data.success)
905+
self.assertEqual(envelope.data.base_data.response_code, "0")
861906

862907
# location
863908
span._attributes = {

0 commit comments

Comments
 (0)