Skip to content

Commit fb7d281

Browse files
committed
Replaced WSGI name callback with request/response hooks
1 parent ebfd098 commit fb7d281

File tree

3 files changed

+60
-32
lines changed

3 files changed

+60
-32
lines changed

Diff for: CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1919
### Added
2020
- `opentelemetry-instrumentation-urllib3` Add urllib3 instrumentation
2121
([#299](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/299))
22+
- `opentelemetry-instrumentation-wsgi` Replaced `name_callback` with `request_hook`
23+
and `response_hook` callbacks.
24+
([#424](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/424))
2225

2326
## [0.19b0](https://github.com/open-telemetry/opentelemetry-python-contrib/releases/tag/v0.19b0) - 2021-03-26
2427

Diff for: instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py

+20-14
Original file line numberDiff line numberDiff line change
@@ -174,11 +174,6 @@ def add_response_attributes(
174174
span.set_status(Status(http_status_to_status_code(status_code)))
175175

176176

177-
def get_default_span_name(environ):
178-
"""Default implementation for name_callback, returns HTTP {METHOD_NAME}."""
179-
return "HTTP {}".format(environ.get("REQUEST_METHOD", "")).strip()
180-
181-
182177
class OpenTelemetryMiddleware:
183178
"""The WSGI application middleware.
184179
@@ -187,21 +182,26 @@ class OpenTelemetryMiddleware:
187182
188183
Args:
189184
wsgi: The WSGI application callable to forward requests to.
190-
name_callback: Callback which calculates a generic span name for an
191-
incoming HTTP request based on the PEP3333 WSGI environ.
192-
Optional: Defaults to get_default_span_name.
185+
request_hook: Optional callback which is called with the server span and WSGI
186+
environ object for every incoming request.
187+
response_hook: Optional callback which is called with the server span,
188+
WSGI environ, status_code and response_headers for every
189+
incoming request.
193190
"""
194191

195-
def __init__(self, wsgi, name_callback=get_default_span_name):
192+
def __init__(self, wsgi, request_hook=None, response_hook=None):
196193
self.wsgi = wsgi
197194
self.tracer = trace.get_tracer(__name__, __version__)
198-
self.name_callback = name_callback
195+
self.request_hook = request_hook
196+
self.response_hook = response_hook
199197

200198
@staticmethod
201-
def _create_start_response(span, start_response):
199+
def _create_start_response(span, start_response, response_hook):
202200
@functools.wraps(start_response)
203201
def _start_response(status, response_headers, *args, **kwargs):
204202
add_response_attributes(span, status, response_headers)
203+
if response_hook:
204+
response_hook(status, response_headers)
205205
return start_response(status, response_headers, *args, **kwargs)
206206

207207
return _start_response
@@ -215,18 +215,24 @@ def __call__(self, environ, start_response):
215215
"""
216216

217217
token = context.attach(extract(environ, getter=wsgi_getter))
218-
span_name = self.name_callback(environ)
219218

220219
span = self.tracer.start_span(
221-
span_name,
220+
"HTTP {}".format(environ.get("REQUEST_METHOD", "")).strip(),
222221
kind=trace.SpanKind.SERVER,
223222
attributes=collect_request_attributes(environ),
224223
)
225224

225+
if self.request_hook:
226+
self.request_hook(span, environ)
227+
228+
response_hook = self.response_hook
229+
if response_hook:
230+
response_hook = functools.partial(response_hook, span, environ)
231+
226232
try:
227233
with trace.use_span(span):
228234
start_response = self._create_start_response(
229-
span, start_response
235+
span, start_response, response_hook
230236
)
231237
iterable = self.wsgi(environ, start_response)
232238
return _end_span_after_iterating(

Diff for: instrumentation/opentelemetry-instrumentation-wsgi/tests/test_wsgi_middleware.py

+37-18
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,13 @@ def error_wsgi_unhandled(environ, start_response):
8181

8282
class TestWsgiApplication(WsgiTestBase):
8383
def validate_response(
84-
self, response, error=None, span_name="HTTP GET", http_method="GET"
84+
self,
85+
response,
86+
error=None,
87+
span_name="HTTP GET",
88+
http_method="GET",
89+
span_attributes=None,
90+
response_headers=None,
8591
):
8692
while True:
8793
try:
@@ -90,10 +96,12 @@ def validate_response(
9096
except StopIteration:
9197
break
9298

99+
expected_headers = [("Content-Type", "text/plain")]
100+
if response_headers:
101+
expected_headers.extend(response_headers)
102+
93103
self.assertEqual(self.status, "200 OK")
94-
self.assertEqual(
95-
self.response_headers, [("Content-Type", "text/plain")]
96-
)
104+
self.assertEqual(self.response_headers, expected_headers)
97105
if error:
98106
self.assertIs(self.exc_info[0], error)
99107
self.assertIsInstance(self.exc_info[1], error)
@@ -115,6 +123,7 @@ def validate_response(
115123
"http.status_text": "OK",
116124
"http.status_code": 200,
117125
}
126+
expected_attributes.update(span_attributes or {})
118127
if http_method is not None:
119128
expected_attributes["http.method"] = http_method
120129
self.assertEqual(span_list[0].attributes, expected_attributes)
@@ -124,6 +133,30 @@ def test_basic_wsgi_call(self):
124133
response = app(self.environ, self.start_response)
125134
self.validate_response(response)
126135

136+
def test_hooks(self):
137+
hook_headers = (
138+
"hook_attr",
139+
"hello otel",
140+
)
141+
142+
def request_hook(span, environ):
143+
span.update_name("name from hook")
144+
145+
def response_hook(span, environ, status_code, response_headers):
146+
span.set_attribute("hook_attr", "hello world")
147+
response_headers.append(hook_headers)
148+
149+
app = otel_wsgi.OpenTelemetryMiddleware(
150+
simple_wsgi, request_hook, response_hook
151+
)
152+
response = app(self.environ, self.start_response)
153+
self.validate_response(
154+
response,
155+
span_name="name from hook",
156+
span_attributes={"hook_attr": "hello world"},
157+
response_headers=(hook_headers,),
158+
)
159+
127160
def test_wsgi_not_recording(self):
128161
mock_tracer = mock.Mock()
129162
mock_span = mock.Mock()
@@ -177,20 +210,6 @@ def test_wsgi_internal_error(self):
177210
span_list[0].status.status_code, StatusCode.ERROR,
178211
)
179212

180-
def test_override_span_name(self):
181-
"""Test that span_names can be overwritten by our callback function."""
182-
span_name = "Dymaxion"
183-
184-
def get_predefined_span_name(scope):
185-
# pylint: disable=unused-argument
186-
return span_name
187-
188-
app = otel_wsgi.OpenTelemetryMiddleware(
189-
simple_wsgi, name_callback=get_predefined_span_name
190-
)
191-
response = app(self.environ, self.start_response)
192-
self.validate_response(response, span_name=span_name)
193-
194213
def test_default_span_name_missing_request_method(self):
195214
"""Test that default span_names with missing request method."""
196215
self.environ.pop("REQUEST_METHOD")

0 commit comments

Comments
 (0)