Skip to content

Commit ede28b2

Browse files
committed
Added callback to override default span-name, default span name to HTTP METHOD
Signed-off-by: Emil Madsen <[email protected]>
1 parent e0184fb commit ede28b2

File tree

2 files changed

+41
-14
lines changed

2 files changed

+41
-14
lines changed

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

+9-9
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,8 @@ def set_status_code(span, status_code):
124124

125125

126126
def get_default_span_name(scope):
127-
"""Calculates a (generic) span name for an incoming HTTP request based on the ASGI scope."""
128-
129-
# TODO: Update once
130-
# https://github.com/open-telemetry/opentelemetry-specification/issues/270
131-
# is resolved
132-
return scope.get("path", "/")
127+
"""Default implementation for name_callback, returns HTTP {METHOD_NAME}."""
128+
return "HTTP " + scope.get("method")
133129

134130

135131
class OpenTelemetryMiddleware:
@@ -140,11 +136,15 @@ class OpenTelemetryMiddleware:
140136
141137
Args:
142138
app: The ASGI application callable to forward requests to.
139+
name_callback: Callback which calculates a generic span name for an
140+
incoming HTTP request based on the ASGI scope.
141+
Optional: Defaults to get_default_span_name.
143142
"""
144143

145-
def __init__(self, app):
144+
def __init__(self, app, name_callback=None):
146145
self.app = guarantee_single_callable(app)
147146
self.tracer = trace.tracer_source().get_tracer(__name__, __version__)
147+
self.name_callback = name_callback or get_default_span_name
148148

149149
async def __call__(self, scope, receive, send):
150150
"""The ASGI application
@@ -156,10 +156,10 @@ async def __call__(self, scope, receive, send):
156156
"""
157157

158158
parent_span = propagators.extract(get_header_from_scope, scope)
159-
span_name = get_default_span_name(scope)
159+
span_name = self.name_callback(scope)
160160

161161
with self.tracer.start_as_current_span(
162-
span_name,
162+
span_name + " (connection)",
163163
parent_span,
164164
kind=trace.SpanKind.SERVER,
165165
attributes=collect_request_attributes(scope),

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

+32-5
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ async def error_asgi(scope, receive, send):
5959

6060

6161
class TestAsgiApplication(AsgiTestBase):
62-
def validate_outputs(self, outputs, error=None):
62+
def validate_outputs(self, outputs, error=None, modifiers=None):
63+
# Ensure modifiers is a list
64+
modifiers = modifiers or []
6365
# Check for expected outputs
6466
self.assertEqual(len(outputs), 2)
6567
response_start = outputs[0]
@@ -89,25 +91,25 @@ def validate_outputs(self, outputs, error=None):
8991
self.assertEqual(len(span_list), 4)
9092
expected = [
9193
{
92-
"name": "/ (http.request)",
94+
"name": "HTTP GET (http.request)",
9395
"kind": trace_api.SpanKind.INTERNAL,
9496
"attributes": {"type": "http.request"},
9597
},
9698
{
97-
"name": "/ (http.response.start)",
99+
"name": "HTTP GET (http.response.start)",
98100
"kind": trace_api.SpanKind.INTERNAL,
99101
"attributes": {
100102
"http.status_code": 200,
101103
"type": "http.response.start",
102104
},
103105
},
104106
{
105-
"name": "/ (http.response.body)",
107+
"name": "HTTP GET (http.response.body)",
106108
"kind": trace_api.SpanKind.INTERNAL,
107109
"attributes": {"type": "http.response.body"},
108110
},
109111
{
110-
"name": "/",
112+
"name": "HTTP GET (connection)",
111113
"kind": trace_api.SpanKind.SERVER,
112114
"attributes": {
113115
"component": "http",
@@ -124,25 +126,50 @@ def validate_outputs(self, outputs, error=None):
124126
},
125127
},
126128
]
129+
# Run our expected modifiers
130+
for modifier in modifiers:
131+
expected = modifier(expected)
132+
# Check that output matches
127133
for span, expected in zip(span_list, expected):
128134
self.assertEqual(span.name, expected["name"])
129135
self.assertEqual(span.kind, expected["kind"])
130136
self.assertEqual(span.attributes, expected["attributes"])
131137

132138
def test_basic_asgi_call(self):
139+
"""Test that spans are emitted as expected."""
133140
app = otel_asgi.OpenTelemetryMiddleware(simple_asgi)
134141
self.seed_app(app)
135142
self.send_default_request()
136143
outputs = self.get_all_output()
137144
self.validate_outputs(outputs)
138145

139146
def test_asgi_exc_info(self):
147+
"""Test that exception information is emitted as expected."""
140148
app = otel_asgi.OpenTelemetryMiddleware(error_asgi)
141149
self.seed_app(app)
142150
self.send_default_request()
143151
outputs = self.get_all_output()
144152
self.validate_outputs(outputs, error=ValueError)
145153

154+
def test_override_span_name(self):
155+
"""Test that span_names can be overwritten by our callback function."""
156+
span_name = "Dymaxion"
157+
def get_predefined_span_name(scope):
158+
return span_name
159+
def update_expected_span_name(expected):
160+
for entry in expected:
161+
entry['name'] = " ".join(
162+
[span_name] + entry['name'].split(' ')[-1:]
163+
)
164+
return expected
165+
app = otel_asgi.OpenTelemetryMiddleware(
166+
simple_asgi, name_callback=get_predefined_span_name
167+
)
168+
self.seed_app(app)
169+
self.send_default_request()
170+
outputs = self.get_all_output()
171+
self.validate_outputs(outputs, modifiers=[update_expected_span_name])
172+
146173

147174
class TestAsgiAttributes(unittest.TestCase):
148175
def setUp(self):

0 commit comments

Comments
 (0)