|
22 | 22 |
|
23 | 23 | import opentelemetry.instrumentation.asgi as otel_asgi |
24 | 24 | from opentelemetry import trace as trace_api |
| 25 | +from opentelemetry.instrumentation._labeler import clear_labeler, get_labeler |
25 | 26 | from opentelemetry.instrumentation._semconv import ( |
26 | 27 | HTTP_DURATION_HISTOGRAM_BUCKETS_NEW, |
27 | 28 | OTEL_SEMCONV_STABILITY_OPT_IN, |
|
108 | 109 | _server_active_requests_count_attrs_old |
109 | 110 | ) |
110 | 111 |
|
| 112 | +_server_active_requests_count_attrs_both = ( |
| 113 | + _server_active_requests_count_attrs_old |
| 114 | +) |
| 115 | +_server_active_requests_count_attrs_both.extend( |
| 116 | + _server_active_requests_count_attrs_new |
| 117 | +) |
| 118 | + |
| 119 | +_custom_attributes = ["custom_attr", "endpoint_type", "feature_flag"] |
| 120 | +_server_duration_attrs_old_with_custom = _server_duration_attrs_old.copy() |
| 121 | +_server_duration_attrs_old_with_custom.append("http.target") |
| 122 | +_server_duration_attrs_old_with_custom.extend(_custom_attributes) |
| 123 | +_server_duration_attrs_new_with_custom = _server_duration_attrs_new.copy() |
| 124 | +_server_duration_attrs_new_with_custom.append("http.route") |
| 125 | +_server_duration_attrs_new_with_custom.extend(_custom_attributes) |
| 126 | + |
| 127 | +_recommended_metrics_attrs_old_with_custom = { |
| 128 | + "http.server.active_requests": _server_active_requests_count_attrs_old, |
| 129 | + "http.server.duration": _server_duration_attrs_old_with_custom, |
| 130 | + "http.server.request.size": _server_duration_attrs_old_with_custom, |
| 131 | + "http.server.response.size": _server_duration_attrs_old_with_custom, |
| 132 | +} |
| 133 | +_recommended_metrics_attrs_new_with_custom = { |
| 134 | + "http.server.active_requests": _server_active_requests_count_attrs_new, |
| 135 | + "http.server.request.duration": _server_duration_attrs_new_with_custom, |
| 136 | + "http.server.request.body.size": _server_duration_attrs_new_with_custom, |
| 137 | + "http.server.response.body.size": _server_duration_attrs_new_with_custom, |
| 138 | +} |
| 139 | +_recommended_metrics_attrs_both_with_custom = { |
| 140 | + "http.server.active_requests": _server_active_requests_count_attrs_both, |
| 141 | + "http.server.duration": _server_duration_attrs_old_with_custom, |
| 142 | + "http.server.request.duration": _server_duration_attrs_new_with_custom, |
| 143 | + "http.server.request.size": _server_duration_attrs_old_with_custom, |
| 144 | + "http.server.request.body.size": _server_duration_attrs_new_with_custom, |
| 145 | + "http.server.response.size": _server_duration_attrs_old_with_custom, |
| 146 | + "http.server.response.body.size": _server_duration_attrs_new_with_custom, |
| 147 | +} |
| 148 | + |
111 | 149 | _SIMULATED_BACKGROUND_TASK_EXECUTION_TIME_S = 0.01 |
112 | 150 |
|
113 | 151 |
|
@@ -254,6 +292,28 @@ async def background_execution_trailers_asgi(scope, receive, send): |
254 | 292 | time.sleep(_SIMULATED_BACKGROUND_TASK_EXECUTION_TIME_S) |
255 | 293 |
|
256 | 294 |
|
| 295 | +async def custom_attrs_asgi(scope, receive, send): |
| 296 | + assert isinstance(scope, dict) |
| 297 | + assert scope["type"] == "http" |
| 298 | + labeler = get_labeler() |
| 299 | + labeler.add("custom_attr", "test_value") |
| 300 | + labeler.add_attributes({"endpoint_type": "test", "feature_flag": True}) |
| 301 | + message = await receive() |
| 302 | + scope["headers"] = [(b"content-length", b"128")] |
| 303 | + if message.get("type") == "http.request": |
| 304 | + await send( |
| 305 | + { |
| 306 | + "type": "http.response.start", |
| 307 | + "status": 200, |
| 308 | + "headers": [ |
| 309 | + [b"Content-Type", b"text/plain"], |
| 310 | + [b"content-length", b"1024"], |
| 311 | + ], |
| 312 | + } |
| 313 | + ) |
| 314 | + await send({"type": "http.response.body", "body": b"*"}) |
| 315 | + |
| 316 | + |
257 | 317 | async def error_asgi(scope, receive, send): |
258 | 318 | assert isinstance(scope, dict) |
259 | 319 | assert scope["type"] == "http" |
@@ -281,6 +341,7 @@ async def error_asgi(scope, receive, send): |
281 | 341 | class TestAsgiApplication(AsyncAsgiTestBase): |
282 | 342 | def setUp(self): |
283 | 343 | super().setUp() |
| 344 | + clear_labeler() |
284 | 345 |
|
285 | 346 | test_name = "" |
286 | 347 | if hasattr(self, "_testMethodName"): |
@@ -1245,6 +1306,57 @@ async def test_asgi_metrics(self): |
1245 | 1306 | ) |
1246 | 1307 | self.assertTrue(number_data_point_seen and histogram_data_point_seen) |
1247 | 1308 |
|
| 1309 | + # pylint: disable=too-many-nested-blocks |
| 1310 | + async def test_asgi_metrics_custom_attributes(self): |
| 1311 | + app = otel_asgi.OpenTelemetryMiddleware(custom_attrs_asgi) |
| 1312 | + self.seed_app(app) |
| 1313 | + await self.send_default_request() |
| 1314 | + await self.get_all_output() |
| 1315 | + self.seed_app(app) |
| 1316 | + await self.send_default_request() |
| 1317 | + await self.get_all_output() |
| 1318 | + self.seed_app(app) |
| 1319 | + await self.send_default_request() |
| 1320 | + await self.get_all_output() |
| 1321 | + metrics_list = self.memory_metrics_reader.get_metrics_data() |
| 1322 | + number_data_point_seen = False |
| 1323 | + histogram_data_point_seen = False |
| 1324 | + |
| 1325 | + self.assertTrue(len(metrics_list.resource_metrics) != 0) |
| 1326 | + for resource_metric in metrics_list.resource_metrics: |
| 1327 | + self.assertTrue(len(resource_metric.scope_metrics) != 0) |
| 1328 | + for scope_metric in resource_metric.scope_metrics: |
| 1329 | + self.assertTrue(len(scope_metric.metrics) != 0) |
| 1330 | + self.assertEqual( |
| 1331 | + scope_metric.scope.name, |
| 1332 | + "opentelemetry.instrumentation.asgi", |
| 1333 | + ) |
| 1334 | + for metric in scope_metric.metrics: |
| 1335 | + self.assertIn(metric.name, _expected_metric_names_old) |
| 1336 | + data_points = list(metric.data.data_points) |
| 1337 | + self.assertEqual(len(data_points), 1) |
| 1338 | + for point in data_points: |
| 1339 | + if isinstance(point, HistogramDataPoint): |
| 1340 | + self.assertEqual(point.count, 3) |
| 1341 | + histogram_data_point_seen = True |
| 1342 | + |
| 1343 | + for attr in point.attributes: |
| 1344 | + self.assertIn( |
| 1345 | + attr, |
| 1346 | + _recommended_metrics_attrs_old_with_custom[ |
| 1347 | + metric.name |
| 1348 | + ], |
| 1349 | + ) |
| 1350 | + |
| 1351 | + if isinstance(point, NumberDataPoint): |
| 1352 | + number_data_point_seen = True |
| 1353 | + |
| 1354 | + for attr in point.attributes: |
| 1355 | + self.assertIn( |
| 1356 | + attr, _recommended_attrs_old[metric.name] |
| 1357 | + ) |
| 1358 | + self.assertTrue(number_data_point_seen and histogram_data_point_seen) |
| 1359 | + |
1248 | 1360 | async def test_asgi_metrics_new_semconv(self): |
1249 | 1361 | # pylint: disable=too-many-nested-blocks |
1250 | 1362 | app = otel_asgi.OpenTelemetryMiddleware(simple_asgi) |
@@ -1290,6 +1402,54 @@ async def test_asgi_metrics_new_semconv(self): |
1290 | 1402 | ) |
1291 | 1403 | self.assertTrue(number_data_point_seen and histogram_data_point_seen) |
1292 | 1404 |
|
| 1405 | + async def test_asgi_metrics_new_semconv_custom_attributes(self): |
| 1406 | + # pylint: disable=too-many-nested-blocks |
| 1407 | + app = otel_asgi.OpenTelemetryMiddleware(custom_attrs_asgi) |
| 1408 | + self.seed_app(app) |
| 1409 | + await self.send_default_request() |
| 1410 | + await self.get_all_output() |
| 1411 | + self.seed_app(app) |
| 1412 | + await self.send_default_request() |
| 1413 | + await self.get_all_output() |
| 1414 | + self.seed_app(app) |
| 1415 | + await self.send_default_request() |
| 1416 | + await self.get_all_output() |
| 1417 | + metrics_list = self.memory_metrics_reader.get_metrics_data() |
| 1418 | + number_data_point_seen = False |
| 1419 | + histogram_data_point_seen = False |
| 1420 | + self.assertTrue(len(metrics_list.resource_metrics) != 0) |
| 1421 | + for resource_metric in metrics_list.resource_metrics: |
| 1422 | + self.assertTrue(len(resource_metric.scope_metrics) != 0) |
| 1423 | + for scope_metric in resource_metric.scope_metrics: |
| 1424 | + self.assertTrue(len(scope_metric.metrics) != 0) |
| 1425 | + self.assertEqual( |
| 1426 | + scope_metric.scope.name, |
| 1427 | + "opentelemetry.instrumentation.asgi", |
| 1428 | + ) |
| 1429 | + for metric in scope_metric.metrics: |
| 1430 | + self.assertIn(metric.name, _expected_metric_names_new) |
| 1431 | + data_points = list(metric.data.data_points) |
| 1432 | + self.assertEqual(len(data_points), 1) |
| 1433 | + for point in data_points: |
| 1434 | + if isinstance(point, HistogramDataPoint): |
| 1435 | + self.assertEqual(point.count, 3) |
| 1436 | + if metric.name == "http.server.request.duration": |
| 1437 | + self.assertEqual( |
| 1438 | + point.explicit_bounds, |
| 1439 | + HTTP_DURATION_HISTOGRAM_BUCKETS_NEW, |
| 1440 | + ) |
| 1441 | + histogram_data_point_seen = True |
| 1442 | + if isinstance(point, NumberDataPoint): |
| 1443 | + number_data_point_seen = True |
| 1444 | + for attr in point.attributes: |
| 1445 | + self.assertIn( |
| 1446 | + attr, |
| 1447 | + _recommended_metrics_attrs_new_with_custom[ |
| 1448 | + metric.name |
| 1449 | + ], |
| 1450 | + ) |
| 1451 | + self.assertTrue(number_data_point_seen and histogram_data_point_seen) |
| 1452 | + |
1293 | 1453 | async def test_asgi_metrics_both_semconv(self): |
1294 | 1454 | # pylint: disable=too-many-nested-blocks |
1295 | 1455 | app = otel_asgi.OpenTelemetryMiddleware(simple_asgi) |
@@ -1335,6 +1495,54 @@ async def test_asgi_metrics_both_semconv(self): |
1335 | 1495 | ) |
1336 | 1496 | self.assertTrue(number_data_point_seen and histogram_data_point_seen) |
1337 | 1497 |
|
| 1498 | + async def test_asgi_metrics_both_semconv_custom_attributes(self): |
| 1499 | + # pylint: disable=too-many-nested-blocks |
| 1500 | + app = otel_asgi.OpenTelemetryMiddleware(custom_attrs_asgi) |
| 1501 | + self.seed_app(app) |
| 1502 | + await self.send_default_request() |
| 1503 | + await self.get_all_output() |
| 1504 | + self.seed_app(app) |
| 1505 | + await self.send_default_request() |
| 1506 | + await self.get_all_output() |
| 1507 | + self.seed_app(app) |
| 1508 | + await self.send_default_request() |
| 1509 | + await self.get_all_output() |
| 1510 | + metrics_list = self.memory_metrics_reader.get_metrics_data() |
| 1511 | + number_data_point_seen = False |
| 1512 | + histogram_data_point_seen = False |
| 1513 | + self.assertTrue(len(metrics_list.resource_metrics) != 0) |
| 1514 | + for resource_metric in metrics_list.resource_metrics: |
| 1515 | + self.assertTrue(len(resource_metric.scope_metrics) != 0) |
| 1516 | + for scope_metric in resource_metric.scope_metrics: |
| 1517 | + self.assertTrue(len(scope_metric.metrics) != 0) |
| 1518 | + self.assertEqual( |
| 1519 | + scope_metric.scope.name, |
| 1520 | + "opentelemetry.instrumentation.asgi", |
| 1521 | + ) |
| 1522 | + for metric in scope_metric.metrics: |
| 1523 | + self.assertIn(metric.name, _expected_metric_names_both) |
| 1524 | + data_points = list(metric.data.data_points) |
| 1525 | + self.assertEqual(len(data_points), 1) |
| 1526 | + for point in data_points: |
| 1527 | + if isinstance(point, HistogramDataPoint): |
| 1528 | + self.assertEqual(point.count, 3) |
| 1529 | + if metric.name == "http.server.request.duration": |
| 1530 | + self.assertEqual( |
| 1531 | + point.explicit_bounds, |
| 1532 | + HTTP_DURATION_HISTOGRAM_BUCKETS_NEW, |
| 1533 | + ) |
| 1534 | + histogram_data_point_seen = True |
| 1535 | + if isinstance(point, NumberDataPoint): |
| 1536 | + number_data_point_seen = True |
| 1537 | + for attr in point.attributes: |
| 1538 | + self.assertIn( |
| 1539 | + attr, |
| 1540 | + _recommended_metrics_attrs_both_with_custom[ |
| 1541 | + metric.name |
| 1542 | + ], |
| 1543 | + ) |
| 1544 | + self.assertTrue(number_data_point_seen and histogram_data_point_seen) |
| 1545 | + |
1338 | 1546 | async def test_basic_metric_success(self): |
1339 | 1547 | app = otel_asgi.OpenTelemetryMiddleware(simple_asgi) |
1340 | 1548 | self.seed_app(app) |
|
0 commit comments