Skip to content

Commit 076144c

Browse files
committed
fix(instrumentation-asgi): remove high cardinal path from span name
1 parent 529178d commit 076144c

File tree

2 files changed

+123
-23
lines changed

2 files changed

+123
-23
lines changed

instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -403,13 +403,9 @@ def get_default_span_details(scope: dict) -> Tuple[str, dict]:
403403
Returns:
404404
a tuple of the span name, and any attributes to attach to the span.
405405
"""
406-
path = scope.get("path", "").strip()
407-
method = scope.get("method", "").strip()
408-
if method and path: # http
409-
return f"{method} {path}", {}
410-
if path: # websocket
411-
return path, {}
412-
return method, {} # http with no path
406+
if scope.get("type") == "http":
407+
return scope.get("method", ""), {}
408+
return scope.get("type", ""), {}
413409

414410

415411
def _collect_target_attribute(

instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py

Lines changed: 120 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,53 @@ async def error_asgi(scope, receive, send):
227227
await send({"type": "http.response.body", "body": b"*"})
228228

229229

230+
# New ASGI app for user update
231+
async def user_update_app(scope, receive, send):
232+
assert scope["type"] == "http"
233+
assert scope["method"] == "PUT"
234+
assert scope["path"].startswith("/api/v3/io/users/")
235+
236+
user_id = scope["path"].split("/")[-1]
237+
238+
message = await receive()
239+
if message["type"] == "http.request":
240+
await send(
241+
{
242+
"type": "http.response.start",
243+
"status": 200,
244+
"headers": [
245+
[b"Content-Type", b"application/json"],
246+
],
247+
}
248+
)
249+
await send(
250+
{
251+
"type": "http.response.body",
252+
"body": f'{{"status": "updated", "user_id": "{user_id}"}}'.encode(),
253+
}
254+
)
255+
256+
257+
# New ASGI app for WebSocket
258+
async def websocket_session_app(scope, receive, send):
259+
assert scope["type"] == "websocket"
260+
assert scope["path"].startswith("/ws/")
261+
262+
session_id = scope["path"].split("/")[-1]
263+
264+
await send({"type": "websocket.accept"})
265+
266+
while True:
267+
event = await receive()
268+
if event["type"] == "websocket.disconnect":
269+
break
270+
if event["type"] == "websocket.receive":
271+
if event.get("text") == "ping":
272+
await send(
273+
{"type": "websocket.send", "text": f"pong:{session_id}"}
274+
)
275+
276+
230277
# pylint: disable=too-many-public-methods
231278
class TestAsgiApplication(AsgiTestBase):
232279
def validate_outputs(self, outputs, error=None, modifiers=None):
@@ -266,25 +313,25 @@ def validate_outputs(self, outputs, error=None, modifiers=None):
266313
span_list = self.memory_exporter.get_finished_spans()
267314
expected = [
268315
{
269-
"name": "GET / http receive",
316+
"name": "GET http receive",
270317
"kind": trace_api.SpanKind.INTERNAL,
271318
"attributes": {"asgi.event.type": "http.request"},
272319
},
273320
{
274-
"name": "GET / http send",
321+
"name": "GET http send",
275322
"kind": trace_api.SpanKind.INTERNAL,
276323
"attributes": {
277324
SpanAttributes.HTTP_STATUS_CODE: 200,
278325
"asgi.event.type": "http.response.start",
279326
},
280327
},
281328
{
282-
"name": "GET / http send",
329+
"name": "GET http send",
283330
"kind": trace_api.SpanKind.INTERNAL,
284331
"attributes": {"asgi.event.type": "http.response.body"},
285332
},
286333
{
287-
"name": "GET /",
334+
"name": "GET",
288335
"kind": trace_api.SpanKind.SERVER,
289336
"attributes": {
290337
SpanAttributes.HTTP_METHOD: "GET",
@@ -360,7 +407,7 @@ def test_long_response(self):
360407

361408
def add_more_body_spans(expected: list):
362409
more_body_span = {
363-
"name": "GET / http send",
410+
"name": "GET http send",
364411
"kind": trace_api.SpanKind.INTERNAL,
365412
"attributes": {"asgi.event.type": "http.response.body"},
366413
}
@@ -398,12 +445,12 @@ def test_trailers(self):
398445

399446
def add_body_and_trailer_span(expected: list):
400447
body_span = {
401-
"name": "GET / http send",
448+
"name": "GET http send",
402449
"kind": trace_api.SpanKind.INTERNAL,
403450
"attributes": {"asgi.event.type": "http.response.body"},
404451
}
405452
trailer_span = {
406-
"name": "GET / http send",
453+
"name": "GET http send",
407454
"kind": trace_api.SpanKind.INTERNAL,
408455
"attributes": {"asgi.event.type": "http.response.trailers"},
409456
}
@@ -434,7 +481,7 @@ def update_expected_span_name(expected):
434481
entry["name"] = span_name
435482
else:
436483
entry["name"] = " ".join(
437-
[span_name] + entry["name"].split(" ")[2:]
484+
[span_name] + entry["name"].split(" ")[1:]
438485
)
439486
return expected
440487

@@ -584,38 +631,38 @@ def test_websocket(self):
584631
self.assertEqual(len(span_list), 6)
585632
expected = [
586633
{
587-
"name": "/ websocket receive",
634+
"name": "websocket websocket receive",
588635
"kind": trace_api.SpanKind.INTERNAL,
589636
"attributes": {"asgi.event.type": "websocket.connect"},
590637
},
591638
{
592-
"name": "/ websocket send",
639+
"name": "websocket websocket send",
593640
"kind": trace_api.SpanKind.INTERNAL,
594641
"attributes": {"asgi.event.type": "websocket.accept"},
595642
},
596643
{
597-
"name": "/ websocket receive",
644+
"name": "websocket websocket receive",
598645
"kind": trace_api.SpanKind.INTERNAL,
599646
"attributes": {
600647
"asgi.event.type": "websocket.receive",
601648
SpanAttributes.HTTP_STATUS_CODE: 200,
602649
},
603650
},
604651
{
605-
"name": "/ websocket send",
652+
"name": "websocket websocket send",
606653
"kind": trace_api.SpanKind.INTERNAL,
607654
"attributes": {
608655
"asgi.event.type": "websocket.send",
609656
SpanAttributes.HTTP_STATUS_CODE: 200,
610657
},
611658
},
612659
{
613-
"name": "/ websocket receive",
660+
"name": "websocket websocket receive",
614661
"kind": trace_api.SpanKind.INTERNAL,
615662
"attributes": {"asgi.event.type": "websocket.disconnect"},
616663
},
617664
{
618-
"name": "/",
665+
"name": "websocket",
619666
"kind": trace_api.SpanKind.SERVER,
620667
"attributes": {
621668
SpanAttributes.HTTP_SCHEME: self.scope["scheme"],
@@ -697,9 +744,9 @@ def update_expected_hook_results(expected):
697744
for entry in expected:
698745
if entry["kind"] == trace_api.SpanKind.SERVER:
699746
entry["name"] = "name from server hook"
700-
elif entry["name"] == "GET / http receive":
747+
elif entry["name"] == "GET http receive":
701748
entry["name"] = "name from client request hook"
702-
elif entry["name"] == "GET / http send":
749+
elif entry["name"] == "GET http send":
703750
entry["attributes"].update({"attr-from-hook": "value"})
704751
return expected
705752

@@ -851,6 +898,63 @@ def test_no_metric_for_websockets(self):
851898
self.get_all_output()
852899
self.assertIsNone(self.memory_metrics_reader.get_metrics_data())
853900

901+
def test_put_request_with_user_id(self):
902+
app = otel_asgi.OpenTelemetryMiddleware(user_update_app)
903+
self.seed_app(app)
904+
self.scope["method"] = "PUT"
905+
self.scope["path"] = "/api/v3/io/users/123"
906+
self.send_input(
907+
{"type": "http.request", "body": b'{"name": "John Doe"}'}
908+
)
909+
910+
outputs = self.get_all_output()
911+
self.assertEqual(len(outputs), 2)
912+
self.assertEqual(outputs[0]["type"], "http.response.start")
913+
self.assertEqual(outputs[0]["status"], 200)
914+
self.assertEqual(outputs[1]["type"], "http.response.body")
915+
self.assertEqual(
916+
outputs[1]["body"], b'{"status": "updated", "user_id": "123"}'
917+
)
918+
919+
span_list = self.memory_exporter.get_finished_spans()
920+
self.assertEqual(len(span_list), 4) # 3 internal spans + 1 server span
921+
server_span = span_list[-1]
922+
self.assertEqual(server_span.name, "PUT")
923+
self.assertEqual(server_span.kind, trace_api.SpanKind.SERVER)
924+
self.assertEqual(
925+
server_span.attributes[SpanAttributes.HTTP_METHOD], "PUT"
926+
)
927+
self.assertEqual(
928+
server_span.attributes[SpanAttributes.HTTP_STATUS_CODE], 200
929+
)
930+
931+
def skip_test_websocket_connection_with_session_id(self):
932+
app = otel_asgi.OpenTelemetryMiddleware(websocket_session_app)
933+
self.seed_app(app)
934+
self.scope["type"] = "websocket"
935+
self.scope["path"] = "/ws/05b55f3f66aa31cbe6a25e7027f7c2cc"
936+
937+
self.send_input({"type": "websocket.connect"})
938+
self.send_input({"type": "websocket.receive", "text": "ping"})
939+
self.send_input({"type": "websocket.disconnect"})
940+
941+
outputs = self.get_all_output()
942+
self.assertEqual(len(outputs), 2)
943+
self.assertEqual(outputs[0]["type"], "websocket.accept")
944+
self.assertEqual(outputs[1]["type"], "websocket.send")
945+
self.assertEqual(
946+
outputs[1]["text"], "pong:05b55f3f66aa31cbe6a25e7027f7c2cc"
947+
)
948+
949+
span_list = self.memory_exporter.get_finished_spans()
950+
self.assertEqual(len(span_list), 6) # 5 internal spans + 1 server span
951+
server_span = span_list[-1]
952+
self.assertEqual(server_span.name, "websocket")
953+
self.assertEqual(server_span.kind, trace_api.SpanKind.SERVER)
954+
self.assertEqual(
955+
server_span.attributes[SpanAttributes.HTTP_SCHEME], "ws"
956+
)
957+
854958

855959
class TestAsgiAttributes(unittest.TestCase):
856960
def setUp(self):

0 commit comments

Comments
 (0)