mirror of
https://github.com/open-telemetry/opentelemetry-python-contrib.git
synced 2025-07-29 21:23:55 +08:00
Replaced WSGI name callback with request/response hooks (#424)
This commit is contained in:
@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
([#387](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/387))
|
([#387](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/387))
|
||||||
- Update redis instrumentation to follow semantic conventions
|
- Update redis instrumentation to follow semantic conventions
|
||||||
([#403](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/403))
|
([#403](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/403))
|
||||||
|
- `opentelemetry-instrumentation-wsgi` Replaced `name_callback` with `request_hook`
|
||||||
|
and `response_hook` callbacks.
|
||||||
|
([#424](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/424))
|
||||||
- Update gRPC instrumentation to better wrap server context
|
- Update gRPC instrumentation to better wrap server context
|
||||||
([#420](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/420))
|
([#420](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/420))
|
||||||
|
|
||||||
@ -38,6 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Remove `http.status_text` from span attributes
|
- Remove `http.status_text` from span attributes
|
||||||
([#406](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/406))
|
([#406](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/406))
|
||||||
|
|
||||||
|
|
||||||
## [0.19b0](https://github.com/open-telemetry/opentelemetry-python-contrib/releases/tag/v0.19b0) - 2021-03-26
|
## [0.19b0](https://github.com/open-telemetry/opentelemetry-python-contrib/releases/tag/v0.19b0) - 2021-03-26
|
||||||
|
|
||||||
- Implement context methods for `_InterceptorChannel`
|
- Implement context methods for `_InterceptorChannel`
|
||||||
|
@ -186,21 +186,26 @@ class OpenTelemetryMiddleware:
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
wsgi: The WSGI application callable to forward requests to.
|
wsgi: The WSGI application callable to forward requests to.
|
||||||
name_callback: Callback which calculates a generic span name for an
|
request_hook: Optional callback which is called with the server span and WSGI
|
||||||
incoming HTTP request based on the PEP3333 WSGI environ.
|
environ object for every incoming request.
|
||||||
Optional: Defaults to get_default_span_name.
|
response_hook: Optional callback which is called with the server span,
|
||||||
|
WSGI environ, status_code and response_headers for every
|
||||||
|
incoming request.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, wsgi, name_callback=get_default_span_name):
|
def __init__(self, wsgi, request_hook=None, response_hook=None):
|
||||||
self.wsgi = wsgi
|
self.wsgi = wsgi
|
||||||
self.tracer = trace.get_tracer(__name__, __version__)
|
self.tracer = trace.get_tracer(__name__, __version__)
|
||||||
self.name_callback = name_callback
|
self.request_hook = request_hook
|
||||||
|
self.response_hook = response_hook
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _create_start_response(span, start_response):
|
def _create_start_response(span, start_response, response_hook):
|
||||||
@functools.wraps(start_response)
|
@functools.wraps(start_response)
|
||||||
def _start_response(status, response_headers, *args, **kwargs):
|
def _start_response(status, response_headers, *args, **kwargs):
|
||||||
add_response_attributes(span, status, response_headers)
|
add_response_attributes(span, status, response_headers)
|
||||||
|
if response_hook:
|
||||||
|
response_hook(status, response_headers)
|
||||||
return start_response(status, response_headers, *args, **kwargs)
|
return start_response(status, response_headers, *args, **kwargs)
|
||||||
|
|
||||||
return _start_response
|
return _start_response
|
||||||
@ -214,18 +219,24 @@ class OpenTelemetryMiddleware:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
token = context.attach(extract(environ, getter=wsgi_getter))
|
token = context.attach(extract(environ, getter=wsgi_getter))
|
||||||
span_name = self.name_callback(environ)
|
|
||||||
|
|
||||||
span = self.tracer.start_span(
|
span = self.tracer.start_span(
|
||||||
span_name,
|
get_default_span_name(environ),
|
||||||
kind=trace.SpanKind.SERVER,
|
kind=trace.SpanKind.SERVER,
|
||||||
attributes=collect_request_attributes(environ),
|
attributes=collect_request_attributes(environ),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.request_hook:
|
||||||
|
self.request_hook(span, environ)
|
||||||
|
|
||||||
|
response_hook = self.response_hook
|
||||||
|
if response_hook:
|
||||||
|
response_hook = functools.partial(response_hook, span, environ)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with trace.use_span(span):
|
with trace.use_span(span):
|
||||||
start_response = self._create_start_response(
|
start_response = self._create_start_response(
|
||||||
span, start_response
|
span, start_response, response_hook
|
||||||
)
|
)
|
||||||
iterable = self.wsgi(environ, start_response)
|
iterable = self.wsgi(environ, start_response)
|
||||||
return _end_span_after_iterating(
|
return _end_span_after_iterating(
|
||||||
|
@ -81,7 +81,13 @@ def error_wsgi_unhandled(environ, start_response):
|
|||||||
|
|
||||||
class TestWsgiApplication(WsgiTestBase):
|
class TestWsgiApplication(WsgiTestBase):
|
||||||
def validate_response(
|
def validate_response(
|
||||||
self, response, error=None, span_name="HTTP GET", http_method="GET"
|
self,
|
||||||
|
response,
|
||||||
|
error=None,
|
||||||
|
span_name="HTTP GET",
|
||||||
|
http_method="GET",
|
||||||
|
span_attributes=None,
|
||||||
|
response_headers=None,
|
||||||
):
|
):
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
@ -90,10 +96,12 @@ class TestWsgiApplication(WsgiTestBase):
|
|||||||
except StopIteration:
|
except StopIteration:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
expected_headers = [("Content-Type", "text/plain")]
|
||||||
|
if response_headers:
|
||||||
|
expected_headers.extend(response_headers)
|
||||||
|
|
||||||
self.assertEqual(self.status, "200 OK")
|
self.assertEqual(self.status, "200 OK")
|
||||||
self.assertEqual(
|
self.assertEqual(self.response_headers, expected_headers)
|
||||||
self.response_headers, [("Content-Type", "text/plain")]
|
|
||||||
)
|
|
||||||
if error:
|
if error:
|
||||||
self.assertIs(self.exc_info[0], error)
|
self.assertIs(self.exc_info[0], error)
|
||||||
self.assertIsInstance(self.exc_info[1], error)
|
self.assertIsInstance(self.exc_info[1], error)
|
||||||
@ -114,6 +122,7 @@ class TestWsgiApplication(WsgiTestBase):
|
|||||||
"http.url": "http://127.0.0.1/",
|
"http.url": "http://127.0.0.1/",
|
||||||
"http.status_code": 200,
|
"http.status_code": 200,
|
||||||
}
|
}
|
||||||
|
expected_attributes.update(span_attributes or {})
|
||||||
if http_method is not None:
|
if http_method is not None:
|
||||||
expected_attributes["http.method"] = http_method
|
expected_attributes["http.method"] = http_method
|
||||||
self.assertEqual(span_list[0].attributes, expected_attributes)
|
self.assertEqual(span_list[0].attributes, expected_attributes)
|
||||||
@ -123,6 +132,30 @@ class TestWsgiApplication(WsgiTestBase):
|
|||||||
response = app(self.environ, self.start_response)
|
response = app(self.environ, self.start_response)
|
||||||
self.validate_response(response)
|
self.validate_response(response)
|
||||||
|
|
||||||
|
def test_hooks(self):
|
||||||
|
hook_headers = (
|
||||||
|
"hook_attr",
|
||||||
|
"hello otel",
|
||||||
|
)
|
||||||
|
|
||||||
|
def request_hook(span, environ):
|
||||||
|
span.update_name("name from hook")
|
||||||
|
|
||||||
|
def response_hook(span, environ, status_code, response_headers):
|
||||||
|
span.set_attribute("hook_attr", "hello world")
|
||||||
|
response_headers.append(hook_headers)
|
||||||
|
|
||||||
|
app = otel_wsgi.OpenTelemetryMiddleware(
|
||||||
|
simple_wsgi, request_hook, response_hook
|
||||||
|
)
|
||||||
|
response = app(self.environ, self.start_response)
|
||||||
|
self.validate_response(
|
||||||
|
response,
|
||||||
|
span_name="name from hook",
|
||||||
|
span_attributes={"hook_attr": "hello world"},
|
||||||
|
response_headers=(hook_headers,),
|
||||||
|
)
|
||||||
|
|
||||||
def test_wsgi_not_recording(self):
|
def test_wsgi_not_recording(self):
|
||||||
mock_tracer = mock.Mock()
|
mock_tracer = mock.Mock()
|
||||||
mock_span = mock.Mock()
|
mock_span = mock.Mock()
|
||||||
@ -176,20 +209,6 @@ class TestWsgiApplication(WsgiTestBase):
|
|||||||
span_list[0].status.status_code, StatusCode.ERROR,
|
span_list[0].status.status_code, StatusCode.ERROR,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_override_span_name(self):
|
|
||||||
"""Test that span_names can be overwritten by our callback function."""
|
|
||||||
span_name = "Dymaxion"
|
|
||||||
|
|
||||||
def get_predefined_span_name(scope):
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
return span_name
|
|
||||||
|
|
||||||
app = otel_wsgi.OpenTelemetryMiddleware(
|
|
||||||
simple_wsgi, name_callback=get_predefined_span_name
|
|
||||||
)
|
|
||||||
response = app(self.environ, self.start_response)
|
|
||||||
self.validate_response(response, span_name=span_name)
|
|
||||||
|
|
||||||
def test_default_span_name_missing_request_method(self):
|
def test_default_span_name_missing_request_method(self):
|
||||||
"""Test that default span_names with missing request method."""
|
"""Test that default span_names with missing request method."""
|
||||||
self.environ.pop("REQUEST_METHOD")
|
self.environ.pop("REQUEST_METHOD")
|
||||||
|
Reference in New Issue
Block a user