Skip to content

Commit 96b0f59

Browse files
authored
Replaced WSGI name callback with request/response hooks (#424)
1 parent e7d26a4 commit 96b0f59

File tree

3 files changed

+61
-27
lines changed

3 files changed

+61
-27
lines changed

Diff for: CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
([#387](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/387))
1616
- Update redis instrumentation to follow semantic conventions
1717
([#403](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/403))
18+
- `opentelemetry-instrumentation-wsgi` Replaced `name_callback` with `request_hook`
19+
and `response_hook` callbacks.
20+
([#424](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/424))
1821
- Update gRPC instrumentation to better wrap server context
1922
([#420](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/420))
2023

@@ -38,6 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3841
- Remove `http.status_text` from span attributes
3942
([#406](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/406))
4043

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

4347
- Implement context methods for `_InterceptorChannel`

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

+20-9
Original file line numberDiff line numberDiff line change
@@ -186,21 +186,26 @@ class OpenTelemetryMiddleware:
186186
187187
Args:
188188
wsgi: The WSGI application callable to forward requests to.
189-
name_callback: Callback which calculates a generic span name for an
190-
incoming HTTP request based on the PEP3333 WSGI environ.
191-
Optional: Defaults to get_default_span_name.
189+
request_hook: Optional callback which is called with the server span and WSGI
190+
environ object for every incoming request.
191+
response_hook: Optional callback which is called with the server span,
192+
WSGI environ, status_code and response_headers for every
193+
incoming request.
192194
"""
193195

194-
def __init__(self, wsgi, name_callback=get_default_span_name):
196+
def __init__(self, wsgi, request_hook=None, response_hook=None):
195197
self.wsgi = wsgi
196198
self.tracer = trace.get_tracer(__name__, __version__)
197-
self.name_callback = name_callback
199+
self.request_hook = request_hook
200+
self.response_hook = response_hook
198201

199202
@staticmethod
200-
def _create_start_response(span, start_response):
203+
def _create_start_response(span, start_response, response_hook):
201204
@functools.wraps(start_response)
202205
def _start_response(status, response_headers, *args, **kwargs):
203206
add_response_attributes(span, status, response_headers)
207+
if response_hook:
208+
response_hook(status, response_headers)
204209
return start_response(status, response_headers, *args, **kwargs)
205210

206211
return _start_response
@@ -214,18 +219,24 @@ def __call__(self, environ, start_response):
214219
"""
215220

216221
token = context.attach(extract(environ, getter=wsgi_getter))
217-
span_name = self.name_callback(environ)
218222

219223
span = self.tracer.start_span(
220-
span_name,
224+
get_default_span_name(environ),
221225
kind=trace.SpanKind.SERVER,
222226
attributes=collect_request_attributes(environ),
223227
)
224228

229+
if self.request_hook:
230+
self.request_hook(span, environ)
231+
232+
response_hook = self.response_hook
233+
if response_hook:
234+
response_hook = functools.partial(response_hook, span, environ)
235+
225236
try:
226237
with trace.use_span(span):
227238
start_response = self._create_start_response(
228-
span, start_response
239+
span, start_response, response_hook
229240
)
230241
iterable = self.wsgi(environ, start_response)
231242
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)
@@ -114,6 +122,7 @@ def validate_response(
114122
"http.url": "http://127.0.0.1/",
115123
"http.status_code": 200,
116124
}
125+
expected_attributes.update(span_attributes or {})
117126
if http_method is not None:
118127
expected_attributes["http.method"] = http_method
119128
self.assertEqual(span_list[0].attributes, expected_attributes)
@@ -123,6 +132,30 @@ def test_basic_wsgi_call(self):
123132
response = app(self.environ, self.start_response)
124133
self.validate_response(response)
125134

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

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

0 commit comments

Comments
 (0)