Skip to content

Commit 3c9f99d

Browse files
authored
feat(wsgi): low cardinality span name and name override (#837)
1 parent b2c0d53 commit 3c9f99d

File tree

4 files changed

+56
-31
lines changed

4 files changed

+56
-31
lines changed

ext/opentelemetry-ext-flask/tests/test_programmatic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ def test_404(self):
118118
resp.close()
119119
span_list = self.memory_exporter.get_finished_spans()
120120
self.assertEqual(len(span_list), 1)
121-
self.assertEqual(span_list[0].name, "/bye")
121+
self.assertEqual(span_list[0].name, "HTTP POST")
122122
self.assertEqual(span_list[0].kind, trace.SpanKind.SERVER)
123123
self.assertEqual(span_list[0].attributes, expected_attrs)
124124

ext/opentelemetry-ext-pyramid/tests/test_programmatic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def test_404(self):
101101
resp.close()
102102
span_list = self.memory_exporter.get_finished_spans()
103103
self.assertEqual(len(span_list), 1)
104-
self.assertEqual(span_list[0].name, "/bye")
104+
self.assertEqual(span_list[0].name, "HTTP POST")
105105
self.assertEqual(span_list[0].kind, trace.SpanKind.SERVER)
106106
self.assertEqual(span_list[0].attributes, expected_attrs)
107107

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

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,15 @@ def collect_request_attributes(environ):
9393

9494
result = {
9595
"component": "http",
96-
"http.method": environ["REQUEST_METHOD"],
97-
"http.server_name": environ["SERVER_NAME"],
98-
"http.scheme": environ["wsgi.url_scheme"],
99-
"host.port": int(environ["SERVER_PORT"]),
96+
"http.method": environ.get("REQUEST_METHOD"),
97+
"http.server_name": environ.get("SERVER_NAME"),
98+
"http.scheme": environ.get("wsgi.url_scheme"),
10099
}
101100

101+
host_port = environ.get("SERVER_PORT")
102+
if host_port is not None:
103+
result.update({"host.port": int(host_port)})
104+
102105
setifnotnone(result, "http.host", environ.get("HTTP_HOST"))
103106
target = environ.get("RAW_URI")
104107
if target is None: # Note: `"" or None is None`
@@ -149,12 +152,8 @@ def add_response_attributes(
149152

150153

151154
def get_default_span_name(environ):
152-
"""Calculates a (generic) span name for an incoming HTTP request based on the PEP3333 conforming WSGI environ."""
153-
154-
# TODO: Update once
155-
# https://github.com/open-telemetry/opentelemetry-specification/issues/270
156-
# is resolved
157-
return environ.get("PATH_INFO", "/")
155+
"""Default implementation for name_callback, returns HTTP {METHOD_NAME}."""
156+
return "HTTP {}".format(environ.get("REQUEST_METHOD", "")).strip()
158157

159158

160159
class OpenTelemetryMiddleware:
@@ -165,11 +164,15 @@ class OpenTelemetryMiddleware:
165164
166165
Args:
167166
wsgi: The WSGI application callable to forward requests to.
167+
name_callback: Callback which calculates a generic span name for an
168+
incoming HTTP request based on the PEP3333 WSGI environ.
169+
Optional: Defaults to get_default_span_name.
168170
"""
169171

170-
def __init__(self, wsgi):
172+
def __init__(self, wsgi, name_callback=get_default_span_name):
171173
self.wsgi = wsgi
172174
self.tracer = trace.get_tracer(__name__, __version__)
175+
self.name_callback = name_callback
173176

174177
@staticmethod
175178
def _create_start_response(span, start_response):
@@ -191,7 +194,7 @@ def __call__(self, environ, start_response):
191194
token = context.attach(
192195
propagators.extract(get_header_from_environ, environ)
193196
)
194-
span_name = get_default_span_name(environ)
197+
span_name = self.name_callback(environ)
195198

196199
span = self.tracer.start_span(
197200
span_name,

ext/opentelemetry-ext-wsgi/tests/test_wsgi_middleware.py

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@ def error_wsgi(environ, start_response):
7474

7575

7676
class TestWsgiApplication(WsgiTestBase):
77-
def validate_response(self, response, error=None):
77+
def validate_response(
78+
self, response, error=None, span_name="HTTP GET", http_method="GET"
79+
):
7880
while True:
7981
try:
8082
value = next(response)
@@ -95,23 +97,22 @@ def validate_response(self, response, error=None):
9597

9698
span_list = self.memory_exporter.get_finished_spans()
9799
self.assertEqual(len(span_list), 1)
98-
self.assertEqual(span_list[0].name, "/")
100+
self.assertEqual(span_list[0].name, span_name)
99101
self.assertEqual(span_list[0].kind, trace_api.SpanKind.SERVER)
100-
self.assertEqual(
101-
span_list[0].attributes,
102-
{
103-
"component": "http",
104-
"http.method": "GET",
105-
"http.server_name": "127.0.0.1",
106-
"http.scheme": "http",
107-
"host.port": 80,
108-
"http.host": "127.0.0.1",
109-
"http.flavor": "1.0",
110-
"http.url": "http://127.0.0.1/",
111-
"http.status_text": "OK",
112-
"http.status_code": 200,
113-
},
114-
)
102+
expected_attributes = {
103+
"component": "http",
104+
"http.server_name": "127.0.0.1",
105+
"http.scheme": "http",
106+
"host.port": 80,
107+
"http.host": "127.0.0.1",
108+
"http.flavor": "1.0",
109+
"http.url": "http://127.0.0.1/",
110+
"http.status_text": "OK",
111+
"http.status_code": 200,
112+
}
113+
if http_method is not None:
114+
expected_attributes["http.method"] = http_method
115+
self.assertEqual(span_list[0].attributes, expected_attributes)
115116

116117
def test_basic_wsgi_call(self):
117118
app = otel_wsgi.OpenTelemetryMiddleware(simple_wsgi)
@@ -147,6 +148,27 @@ def test_wsgi_exc_info(self):
147148
response = app(self.environ, self.start_response)
148149
self.validate_response(response, error=ValueError)
149150

151+
def test_override_span_name(self):
152+
"""Test that span_names can be overwritten by our callback function."""
153+
span_name = "Dymaxion"
154+
155+
def get_predefined_span_name(scope):
156+
# pylint: disable=unused-argument
157+
return span_name
158+
159+
app = otel_wsgi.OpenTelemetryMiddleware(
160+
simple_wsgi, name_callback=get_predefined_span_name
161+
)
162+
response = app(self.environ, self.start_response)
163+
self.validate_response(response, span_name=span_name)
164+
165+
def test_default_span_name_missing_request_method(self):
166+
"""Test that default span_names with missing request method."""
167+
self.environ.pop("REQUEST_METHOD")
168+
app = otel_wsgi.OpenTelemetryMiddleware(simple_wsgi)
169+
response = app(self.environ, self.start_response)
170+
self.validate_response(response, span_name="HTTP", http_method=None)
171+
150172

151173
class TestWsgiAttributes(unittest.TestCase):
152174
def setUp(self):

0 commit comments

Comments
 (0)