mirror of
https://github.com/open-telemetry/opentelemetry-python-contrib.git
synced 2025-08-03 04:10:48 +08:00
461 lines
18 KiB
Python
461 lines
18 KiB
Python
# 3rd party
|
|
from django.test import modify_settings
|
|
from django.db import connections
|
|
|
|
# project
|
|
from ddtrace import config
|
|
from ddtrace.constants import ANALYTICS_SAMPLE_RATE_KEY, SAMPLING_PRIORITY_KEY
|
|
from ddtrace.contrib.django.db import unpatch_conn
|
|
from ddtrace.ext import errors, http
|
|
|
|
# testing
|
|
from tests.opentracer.utils import init_tracer
|
|
from .compat import reverse
|
|
from .utils import DjangoTraceTestCase, override_ddtrace_settings
|
|
from ...utils import assert_span_http_status_code
|
|
|
|
|
|
class DjangoMiddlewareTest(DjangoTraceTestCase):
|
|
"""
|
|
Ensures that the middleware traces all Django internals
|
|
"""
|
|
def test_middleware_trace_request(self, query_string=''):
|
|
# ensures that the internals are properly traced
|
|
url = reverse('users-list')
|
|
if query_string:
|
|
fqs = '?' + query_string
|
|
else:
|
|
fqs = ''
|
|
response = self.client.get(url + fqs)
|
|
assert response.status_code == 200
|
|
|
|
# check for spans
|
|
spans = self.tracer.writer.pop()
|
|
assert len(spans) == 3
|
|
sp_request = spans[0]
|
|
sp_template = spans[1]
|
|
sp_database = spans[2]
|
|
assert sp_database.get_tag('django.db.vendor') == 'sqlite'
|
|
assert sp_template.get_tag('django.template_name') == 'users_list.html'
|
|
assert_span_http_status_code(sp_request, 200)
|
|
assert sp_request.get_tag(http.URL) == 'http://testserver/users/'
|
|
assert sp_request.get_tag('django.user.is_authenticated') == 'False'
|
|
assert sp_request.get_tag('http.method') == 'GET'
|
|
assert sp_request.span_type == 'web'
|
|
assert sp_request.resource == 'tests.contrib.django.app.views.UserList'
|
|
if config.django.trace_query_string:
|
|
assert sp_request.get_tag(http.QUERY_STRING) == query_string
|
|
else:
|
|
assert http.QUERY_STRING not in sp_request.meta
|
|
|
|
def test_middleware_trace_request_qs(self):
|
|
return self.test_middleware_trace_request('foo=bar')
|
|
|
|
def test_middleware_trace_request_multi_qs(self):
|
|
return self.test_middleware_trace_request('foo=bar&foo=baz&x=y')
|
|
|
|
def test_middleware_trace_request_no_qs_trace(self):
|
|
with self.override_global_config(dict(trace_query_string=True)):
|
|
return self.test_middleware_trace_request()
|
|
|
|
def test_middleware_trace_request_qs_trace(self):
|
|
with self.override_global_config(dict(trace_query_string=True)):
|
|
return self.test_middleware_trace_request('foo=bar')
|
|
|
|
def test_middleware_trace_request_multi_qs_trace(self):
|
|
with self.override_global_config(dict(trace_query_string=True)):
|
|
return self.test_middleware_trace_request('foo=bar&foo=baz&x=y')
|
|
|
|
def test_analytics_global_on_integration_default(self):
|
|
"""
|
|
When making a request
|
|
When an integration trace search is not event sample rate is not set and globally trace search is enabled
|
|
We expect the root span to have the appropriate tag
|
|
"""
|
|
with self.override_global_config(dict(analytics_enabled=True)):
|
|
url = reverse('users-list')
|
|
response = self.client.get(url)
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
spans = self.tracer.writer.pop()
|
|
assert len(spans) == 3
|
|
sp_request = spans[0]
|
|
sp_template = spans[1]
|
|
sp_database = spans[2]
|
|
self.assertEqual(sp_request.name, 'django.request')
|
|
self.assertEqual(sp_request.get_metric(ANALYTICS_SAMPLE_RATE_KEY), 1.0)
|
|
self.assertIsNone(sp_template.get_metric(ANALYTICS_SAMPLE_RATE_KEY))
|
|
self.assertIsNone(sp_database.get_metric(ANALYTICS_SAMPLE_RATE_KEY))
|
|
|
|
@override_ddtrace_settings(ANALYTICS_ENABLED=True, ANALYTICS_SAMPLE_RATE=0.5)
|
|
def test_analytics_global_on_integration_on(self):
|
|
"""
|
|
When making a request
|
|
When an integration trace search is enabled and sample rate is set and globally trace search is enabled
|
|
We expect the root span to have the appropriate tag
|
|
"""
|
|
with self.override_global_config(dict(analytics_enabled=True)):
|
|
url = reverse('users-list')
|
|
response = self.client.get(url)
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
spans = self.tracer.writer.pop()
|
|
assert len(spans) == 3
|
|
sp_request = spans[0]
|
|
sp_template = spans[1]
|
|
sp_database = spans[2]
|
|
self.assertEqual(sp_request.name, 'django.request')
|
|
self.assertEqual(sp_request.get_metric(ANALYTICS_SAMPLE_RATE_KEY), 0.5)
|
|
self.assertIsNone(sp_template.get_metric(ANALYTICS_SAMPLE_RATE_KEY))
|
|
self.assertIsNone(sp_database.get_metric(ANALYTICS_SAMPLE_RATE_KEY))
|
|
|
|
def test_analytics_global_off_integration_default(self):
|
|
"""
|
|
When making a request
|
|
When an integration trace search is not set and sample rate is set and globally trace search is disabled
|
|
We expect the root span to not include tag
|
|
"""
|
|
with self.override_global_config(dict(analytics_enabled=False)):
|
|
url = reverse('users-list')
|
|
response = self.client.get(url)
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
spans = self.tracer.writer.pop()
|
|
assert len(spans) == 3
|
|
sp_request = spans[0]
|
|
sp_template = spans[1]
|
|
sp_database = spans[2]
|
|
self.assertEqual(sp_request.name, 'django.request')
|
|
self.assertIsNone(sp_request.get_metric(ANALYTICS_SAMPLE_RATE_KEY))
|
|
self.assertIsNone(sp_template.get_metric(ANALYTICS_SAMPLE_RATE_KEY))
|
|
self.assertIsNone(sp_database.get_metric(ANALYTICS_SAMPLE_RATE_KEY))
|
|
|
|
@override_ddtrace_settings(ANALYTICS_ENABLED=True, ANALYTICS_SAMPLE_RATE=0.5)
|
|
def test_analytics_global_off_integration_on(self):
|
|
"""
|
|
When making a request
|
|
When an integration trace search is enabled and sample rate is set and globally trace search is disabled
|
|
We expect the root span to have the appropriate tag
|
|
"""
|
|
with self.override_global_config(dict(analytics_enabled=False)):
|
|
url = reverse('users-list')
|
|
response = self.client.get(url)
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
spans = self.tracer.writer.pop()
|
|
assert len(spans) == 3
|
|
sp_request = spans[0]
|
|
sp_template = spans[1]
|
|
sp_database = spans[2]
|
|
self.assertEqual(sp_request.name, 'django.request')
|
|
self.assertEqual(sp_request.get_metric(ANALYTICS_SAMPLE_RATE_KEY), 0.5)
|
|
self.assertIsNone(sp_template.get_metric(ANALYTICS_SAMPLE_RATE_KEY))
|
|
self.assertIsNone(sp_database.get_metric(ANALYTICS_SAMPLE_RATE_KEY))
|
|
|
|
@override_ddtrace_settings(ANALYTICS_ENABLED=True, ANALYTICS_SAMPLE_RATE=None)
|
|
def test_analytics_global_off_integration_on_and_none(self):
|
|
"""
|
|
When making a request
|
|
When an integration trace search is enabled
|
|
Sample rate is set to None
|
|
Globally trace search is disabled
|
|
We expect the root span to have the appropriate tag
|
|
"""
|
|
with self.override_global_config(dict(analytics_enabled=False)):
|
|
url = reverse('users-list')
|
|
response = self.client.get(url)
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
spans = self.tracer.writer.pop()
|
|
assert len(spans) == 3
|
|
sp_request = spans[0]
|
|
sp_template = spans[1]
|
|
sp_database = spans[2]
|
|
self.assertEqual(sp_request.name, 'django.request')
|
|
assert sp_request.get_metric(ANALYTICS_SAMPLE_RATE_KEY) is None
|
|
assert sp_template.get_metric(ANALYTICS_SAMPLE_RATE_KEY) is None
|
|
assert sp_database.get_metric(ANALYTICS_SAMPLE_RATE_KEY) is None
|
|
|
|
def test_database_patch(self):
|
|
# We want to test that a connection-recreation event causes connections
|
|
# to get repatched. However since django tests are a atomic transaction
|
|
# we can't change the connection. Instead we test that the connection
|
|
# does get repatched if it's not patched.
|
|
for conn in connections.all():
|
|
unpatch_conn(conn)
|
|
# ensures that the internals are properly traced
|
|
url = reverse('users-list')
|
|
response = self.client.get(url)
|
|
assert response.status_code == 200
|
|
|
|
# We would be missing span #3, the database span, if the connection
|
|
# wasn't patched.
|
|
spans = self.tracer.writer.pop()
|
|
assert len(spans) == 3
|
|
assert spans[0].name == 'django.request'
|
|
assert spans[1].name == 'django.template'
|
|
assert spans[2].name == 'sqlite.query'
|
|
|
|
def test_middleware_trace_errors(self):
|
|
# ensures that the internals are properly traced
|
|
url = reverse('forbidden-view')
|
|
response = self.client.get(url)
|
|
assert response.status_code == 403
|
|
|
|
# check for spans
|
|
spans = self.tracer.writer.pop()
|
|
assert len(spans) == 1
|
|
span = spans[0]
|
|
assert_span_http_status_code(span, 403)
|
|
assert span.get_tag(http.URL) == 'http://testserver/fail-view/'
|
|
assert span.resource == 'tests.contrib.django.app.views.ForbiddenView'
|
|
|
|
def test_middleware_trace_function_based_view(self):
|
|
# ensures that the internals are properly traced when using a function views
|
|
url = reverse('fn-view')
|
|
response = self.client.get(url)
|
|
assert response.status_code == 200
|
|
|
|
# check for spans
|
|
spans = self.tracer.writer.pop()
|
|
assert len(spans) == 1
|
|
span = spans[0]
|
|
assert_span_http_status_code(span, 200)
|
|
assert span.get_tag(http.URL) == 'http://testserver/fn-view/'
|
|
assert span.resource == 'tests.contrib.django.app.views.function_view'
|
|
|
|
def test_middleware_trace_error_500(self):
|
|
# ensures we trace exceptions generated by views
|
|
url = reverse('error-500')
|
|
response = self.client.get(url)
|
|
assert response.status_code == 500
|
|
|
|
# check for spans
|
|
spans = self.tracer.writer.pop()
|
|
assert len(spans) == 1
|
|
span = spans[0]
|
|
assert span.error == 1
|
|
assert_span_http_status_code(span, 500)
|
|
assert span.get_tag(http.URL) == 'http://testserver/error-500/'
|
|
assert span.resource == 'tests.contrib.django.app.views.error_500'
|
|
assert 'Error 500' in span.get_tag('error.stack')
|
|
|
|
def test_middleware_trace_callable_view(self):
|
|
# ensures that the internals are properly traced when using callable views
|
|
url = reverse('feed-view')
|
|
response = self.client.get(url)
|
|
assert response.status_code == 200
|
|
|
|
# check for spans
|
|
spans = self.tracer.writer.pop()
|
|
assert len(spans) == 1
|
|
span = spans[0]
|
|
assert_span_http_status_code(span, 200)
|
|
assert span.get_tag(http.URL) == 'http://testserver/feed-view/'
|
|
assert span.resource == 'tests.contrib.django.app.views.FeedView'
|
|
|
|
def test_middleware_trace_partial_based_view(self):
|
|
# ensures that the internals are properly traced when using a function views
|
|
url = reverse('partial-view')
|
|
response = self.client.get(url)
|
|
assert response.status_code == 200
|
|
|
|
# check for spans
|
|
spans = self.tracer.writer.pop()
|
|
assert len(spans) == 1
|
|
span = spans[0]
|
|
assert_span_http_status_code(span, 200)
|
|
assert span.get_tag(http.URL) == 'http://testserver/partial-view/'
|
|
assert span.resource == 'partial'
|
|
|
|
def test_middleware_trace_lambda_based_view(self):
|
|
# ensures that the internals are properly traced when using a function views
|
|
url = reverse('lambda-view')
|
|
response = self.client.get(url)
|
|
assert response.status_code == 200
|
|
|
|
# check for spans
|
|
spans = self.tracer.writer.pop()
|
|
assert len(spans) == 1
|
|
span = spans[0]
|
|
assert_span_http_status_code(span, 200)
|
|
assert span.get_tag(http.URL) == 'http://testserver/lambda-view/'
|
|
assert span.resource == 'tests.contrib.django.app.views.<lambda>'
|
|
|
|
@modify_settings(
|
|
MIDDLEWARE={
|
|
'remove': 'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
},
|
|
MIDDLEWARE_CLASSES={
|
|
'remove': 'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
},
|
|
)
|
|
def test_middleware_without_user(self):
|
|
# remove the AuthenticationMiddleware so that the ``request``
|
|
# object doesn't have the ``user`` field
|
|
url = reverse('users-list')
|
|
response = self.client.get(url)
|
|
assert response.status_code == 200
|
|
|
|
# check for spans
|
|
spans = self.tracer.writer.pop()
|
|
assert len(spans) == 3
|
|
sp_request = spans[0]
|
|
assert_span_http_status_code(sp_request, 200)
|
|
assert sp_request.get_tag('django.user.is_authenticated') is None
|
|
|
|
def test_middleware_propagation(self):
|
|
# ensures that we properly propagate http context
|
|
url = reverse('users-list')
|
|
headers = {
|
|
'x-datadog-trace-id': '100',
|
|
'x-datadog-parent-id': '42',
|
|
'x-datadog-sampling-priority': '2',
|
|
}
|
|
response = self.client.get(url, **headers)
|
|
assert response.status_code == 200
|
|
|
|
# check for spans
|
|
spans = self.tracer.writer.pop()
|
|
assert len(spans) == 3
|
|
sp_request = spans[0]
|
|
|
|
# Check for proper propagated attributes
|
|
assert sp_request.trace_id == 100
|
|
assert sp_request.parent_id == 42
|
|
assert sp_request.get_metric(SAMPLING_PRIORITY_KEY) == 2
|
|
|
|
@override_ddtrace_settings(DISTRIBUTED_TRACING=False)
|
|
def test_middleware_no_propagation(self):
|
|
# ensures that we properly propagate http context
|
|
url = reverse('users-list')
|
|
headers = {
|
|
'x-datadog-trace-id': '100',
|
|
'x-datadog-parent-id': '42',
|
|
'x-datadog-sampling-priority': '2',
|
|
}
|
|
response = self.client.get(url, **headers)
|
|
assert response.status_code == 200
|
|
|
|
# check for spans
|
|
spans = self.tracer.writer.pop()
|
|
assert len(spans) == 3
|
|
sp_request = spans[0]
|
|
|
|
# Check that propagation didn't happen
|
|
assert sp_request.trace_id != 100
|
|
assert sp_request.parent_id != 42
|
|
assert sp_request.get_metric(SAMPLING_PRIORITY_KEY) != 2
|
|
|
|
@modify_settings(
|
|
MIDDLEWARE={
|
|
'append': 'tests.contrib.django.app.middlewares.HandleErrorMiddlewareSuccess',
|
|
},
|
|
MIDDLEWARE_CLASSES={
|
|
'append': 'tests.contrib.django.app.middlewares.HandleErrorMiddlewareSuccess',
|
|
},
|
|
)
|
|
def test_middleware_handled_view_exception_success(self):
|
|
""" Test when an exception is raised in a view and then handled, that
|
|
the resulting span does not possess error properties.
|
|
"""
|
|
url = reverse('error-500')
|
|
response = self.client.get(url)
|
|
assert response.status_code == 200
|
|
|
|
spans = self.tracer.writer.pop()
|
|
assert len(spans) == 1
|
|
|
|
sp_request = spans[0]
|
|
|
|
assert sp_request.error == 0
|
|
assert sp_request.get_tag(errors.ERROR_STACK) is None
|
|
assert sp_request.get_tag(errors.ERROR_MSG) is None
|
|
assert sp_request.get_tag(errors.ERROR_TYPE) is None
|
|
|
|
@modify_settings(
|
|
MIDDLEWARE={
|
|
'append': 'tests.contrib.django.app.middlewares.HandleErrorMiddlewareClientError',
|
|
},
|
|
MIDDLEWARE_CLASSES={
|
|
'append': 'tests.contrib.django.app.middlewares.HandleErrorMiddlewareClientError',
|
|
},
|
|
)
|
|
def test_middleware_handled_view_exception_client_error(self):
|
|
""" Test the case that when an exception is raised in a view and then
|
|
handled, that the resulting span does not possess error properties.
|
|
"""
|
|
url = reverse('error-500')
|
|
response = self.client.get(url)
|
|
assert response.status_code == 404
|
|
|
|
spans = self.tracer.writer.pop()
|
|
assert len(spans) == 1
|
|
|
|
sp_request = spans[0]
|
|
|
|
assert sp_request.error == 0
|
|
assert sp_request.get_tag(errors.ERROR_STACK) is None
|
|
assert sp_request.get_tag(errors.ERROR_MSG) is None
|
|
assert sp_request.get_tag(errors.ERROR_TYPE) is None
|
|
|
|
def test_middleware_trace_request_ot(self):
|
|
"""OpenTracing version of test_middleware_trace_request."""
|
|
ot_tracer = init_tracer('my_svc', self.tracer)
|
|
|
|
# ensures that the internals are properly traced
|
|
url = reverse('users-list')
|
|
with ot_tracer.start_active_span('ot_span'):
|
|
response = self.client.get(url)
|
|
assert response.status_code == 200
|
|
|
|
# check for spans
|
|
spans = self.tracer.writer.pop()
|
|
assert len(spans) == 4
|
|
ot_span = spans[0]
|
|
sp_request = spans[1]
|
|
sp_template = spans[2]
|
|
sp_database = spans[3]
|
|
|
|
# confirm parenting
|
|
assert ot_span.parent_id is None
|
|
assert sp_request.parent_id == ot_span.span_id
|
|
|
|
assert ot_span.resource == 'ot_span'
|
|
assert ot_span.service == 'my_svc'
|
|
|
|
assert sp_database.get_tag('django.db.vendor') == 'sqlite'
|
|
assert sp_template.get_tag('django.template_name') == 'users_list.html'
|
|
assert_span_http_status_code(sp_request, 200)
|
|
assert sp_request.get_tag(http.URL) == 'http://testserver/users/'
|
|
assert sp_request.get_tag('django.user.is_authenticated') == 'False'
|
|
assert sp_request.get_tag('http.method') == 'GET'
|
|
|
|
def test_middleware_trace_request_404(self):
|
|
"""
|
|
When making a request to an unknown url in django
|
|
when we do not have a 404 view handler set
|
|
we set a resource name for the default view handler
|
|
"""
|
|
response = self.client.get('/unknown-url')
|
|
assert response.status_code == 404
|
|
|
|
# check for spans
|
|
spans = self.tracer.writer.pop()
|
|
assert len(spans) == 2
|
|
sp_request = spans[0]
|
|
sp_template = spans[1]
|
|
|
|
# Template
|
|
# DEV: The template name is `unknown` because unless they define a `404.html`
|
|
# django generates the template from a string, which will not have a `Template.name` set
|
|
assert sp_template.get_tag('django.template_name') == 'unknown'
|
|
|
|
# Request
|
|
assert_span_http_status_code(sp_request, 404)
|
|
assert sp_request.get_tag(http.URL) == 'http://testserver/unknown-url'
|
|
assert sp_request.get_tag('django.user.is_authenticated') == 'False'
|
|
assert sp_request.get_tag('http.method') == 'GET'
|
|
assert sp_request.span_type == 'web'
|
|
assert sp_request.resource == 'django.views.defaults.page_not_found'
|