mirror of
https://github.com/open-telemetry/opentelemetry-python-contrib.git
synced 2025-08-02 02:52:18 +08:00
492 lines
20 KiB
Python
492 lines
20 KiB
Python
from .web.app import CustomDefaultHandler
|
|
from .utils import TornadoTestCase
|
|
|
|
from ddtrace import config
|
|
from ddtrace.constants import SAMPLING_PRIORITY_KEY, ORIGIN_KEY, ANALYTICS_SAMPLE_RATE_KEY
|
|
from ddtrace.ext import http
|
|
import pytest
|
|
import tornado
|
|
|
|
from tests.opentracer.utils import init_tracer
|
|
from ...utils import assert_span_http_status_code
|
|
|
|
|
|
class TestTornadoWeb(TornadoTestCase):
|
|
"""
|
|
Ensure that Tornado web handlers are properly traced.
|
|
"""
|
|
def test_success_handler(self, query_string=''):
|
|
# it should trace a handler that returns 200
|
|
if query_string:
|
|
fqs = '?' + query_string
|
|
else:
|
|
fqs = ''
|
|
response = self.fetch('/success/' + fqs)
|
|
assert 200 == response.code
|
|
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 1 == len(traces)
|
|
assert 1 == len(traces[0])
|
|
|
|
request_span = traces[0][0]
|
|
assert 'tornado-web' == request_span.service
|
|
assert 'tornado.request' == request_span.name
|
|
assert 'web' == request_span.span_type
|
|
assert 'tests.contrib.tornado.web.app.SuccessHandler' == request_span.resource
|
|
assert 'GET' == request_span.get_tag('http.method')
|
|
assert_span_http_status_code(request_span, 200)
|
|
assert self.get_url('/success/') == request_span.get_tag(http.URL)
|
|
if config.tornado.trace_query_string:
|
|
assert query_string == request_span.get_tag(http.QUERY_STRING)
|
|
else:
|
|
assert http.QUERY_STRING not in request_span.meta
|
|
assert 0 == request_span.error
|
|
|
|
def test_success_handler_query_string(self):
|
|
self.test_success_handler('foo=bar')
|
|
|
|
def test_success_handler_query_string_trace(self):
|
|
with self.override_http_config('tornado', dict(trace_query_string=True)):
|
|
self.test_success_handler('foo=bar')
|
|
|
|
def test_nested_handler(self):
|
|
# it should trace a handler that calls the tracer.trace() method
|
|
# using the automatic Context retrieval
|
|
response = self.fetch('/nested/')
|
|
assert 200 == response.code
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 1 == len(traces)
|
|
assert 2 == len(traces[0])
|
|
# check request span
|
|
request_span = traces[0][0]
|
|
assert 'tornado-web' == request_span.service
|
|
assert 'tornado.request' == request_span.name
|
|
assert 'web' == request_span.span_type
|
|
assert 'tests.contrib.tornado.web.app.NestedHandler' == request_span.resource
|
|
assert 'GET' == request_span.get_tag('http.method')
|
|
assert_span_http_status_code(request_span, 200)
|
|
assert self.get_url('/nested/') == request_span.get_tag(http.URL)
|
|
assert 0 == request_span.error
|
|
# check nested span
|
|
nested_span = traces[0][1]
|
|
assert 'tornado-web' == nested_span.service
|
|
assert 'tornado.sleep' == nested_span.name
|
|
assert 0 == nested_span.error
|
|
# check durations because of the yield sleep
|
|
assert request_span.duration >= 0.05
|
|
assert nested_span.duration >= 0.05
|
|
|
|
def test_exception_handler(self):
|
|
# it should trace a handler that raises an exception
|
|
response = self.fetch('/exception/')
|
|
assert 500 == response.code
|
|
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 1 == len(traces)
|
|
assert 1 == len(traces[0])
|
|
|
|
request_span = traces[0][0]
|
|
assert 'tornado-web' == request_span.service
|
|
assert 'tornado.request' == request_span.name
|
|
assert 'web' == request_span.span_type
|
|
assert 'tests.contrib.tornado.web.app.ExceptionHandler' == request_span.resource
|
|
assert 'GET' == request_span.get_tag('http.method')
|
|
assert_span_http_status_code(request_span, 500)
|
|
assert self.get_url('/exception/') == request_span.get_tag(http.URL)
|
|
assert 1 == request_span.error
|
|
assert 'Ouch!' == request_span.get_tag('error.msg')
|
|
assert 'Exception: Ouch!' in request_span.get_tag('error.stack')
|
|
|
|
def test_http_exception_handler(self):
|
|
# it should trace a handler that raises a Tornado HTTPError
|
|
response = self.fetch('/http_exception/')
|
|
assert 501 == response.code
|
|
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 1 == len(traces)
|
|
assert 1 == len(traces[0])
|
|
|
|
request_span = traces[0][0]
|
|
assert 'tornado-web' == request_span.service
|
|
assert 'tornado.request' == request_span.name
|
|
assert 'web' == request_span.span_type
|
|
assert 'tests.contrib.tornado.web.app.HTTPExceptionHandler' == request_span.resource
|
|
assert 'GET' == request_span.get_tag('http.method')
|
|
assert_span_http_status_code(request_span, 501)
|
|
assert self.get_url('/http_exception/') == request_span.get_tag(http.URL)
|
|
assert 1 == request_span.error
|
|
assert 'HTTP 501: Not Implemented (unavailable)' == request_span.get_tag('error.msg')
|
|
assert 'HTTP 501: Not Implemented (unavailable)' in request_span.get_tag('error.stack')
|
|
|
|
def test_http_exception_500_handler(self):
|
|
# it should trace a handler that raises a Tornado HTTPError
|
|
response = self.fetch('/http_exception_500/')
|
|
assert 500 == response.code
|
|
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 1 == len(traces)
|
|
assert 1 == len(traces[0])
|
|
|
|
request_span = traces[0][0]
|
|
assert 'tornado-web' == request_span.service
|
|
assert 'tornado.request' == request_span.name
|
|
assert 'web' == request_span.span_type
|
|
assert 'tests.contrib.tornado.web.app.HTTPException500Handler' == request_span.resource
|
|
assert 'GET' == request_span.get_tag('http.method')
|
|
assert_span_http_status_code(request_span, 500)
|
|
assert self.get_url('/http_exception_500/') == request_span.get_tag(http.URL)
|
|
assert 1 == request_span.error
|
|
assert 'HTTP 500: Server Error (server error)' == request_span.get_tag('error.msg')
|
|
assert 'HTTP 500: Server Error (server error)' in request_span.get_tag('error.stack')
|
|
|
|
def test_sync_success_handler(self):
|
|
# it should trace a synchronous handler that returns 200
|
|
response = self.fetch('/sync_success/')
|
|
assert 200 == response.code
|
|
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 1 == len(traces)
|
|
assert 1 == len(traces[0])
|
|
|
|
request_span = traces[0][0]
|
|
assert 'tornado-web' == request_span.service
|
|
assert 'tornado.request' == request_span.name
|
|
assert 'web' == request_span.span_type
|
|
assert 'tests.contrib.tornado.web.app.SyncSuccessHandler' == request_span.resource
|
|
assert 'GET' == request_span.get_tag('http.method')
|
|
assert_span_http_status_code(request_span, 200)
|
|
assert self.get_url('/sync_success/') == request_span.get_tag(http.URL)
|
|
assert 0 == request_span.error
|
|
|
|
def test_sync_exception_handler(self):
|
|
# it should trace a handler that raises an exception
|
|
response = self.fetch('/sync_exception/')
|
|
assert 500 == response.code
|
|
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 1 == len(traces)
|
|
assert 1 == len(traces[0])
|
|
|
|
request_span = traces[0][0]
|
|
assert 'tornado-web' == request_span.service
|
|
assert 'tornado.request' == request_span.name
|
|
assert 'web' == request_span.span_type
|
|
assert 'tests.contrib.tornado.web.app.SyncExceptionHandler' == request_span.resource
|
|
assert 'GET' == request_span.get_tag('http.method')
|
|
assert_span_http_status_code(request_span, 500)
|
|
assert self.get_url('/sync_exception/') == request_span.get_tag(http.URL)
|
|
assert 1 == request_span.error
|
|
assert 'Ouch!' == request_span.get_tag('error.msg')
|
|
assert 'Exception: Ouch!' in request_span.get_tag('error.stack')
|
|
|
|
def test_404_handler(self):
|
|
# it should trace 404
|
|
response = self.fetch('/does_not_exist/')
|
|
assert 404 == response.code
|
|
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 1 == len(traces)
|
|
assert 1 == len(traces[0])
|
|
|
|
request_span = traces[0][0]
|
|
assert 'tornado-web' == request_span.service
|
|
assert 'tornado.request' == request_span.name
|
|
assert 'web' == request_span.span_type
|
|
assert 'tornado.web.ErrorHandler' == request_span.resource
|
|
assert 'GET' == request_span.get_tag('http.method')
|
|
assert_span_http_status_code(request_span, 404)
|
|
assert self.get_url('/does_not_exist/') == request_span.get_tag(http.URL)
|
|
assert 0 == request_span.error
|
|
|
|
def test_redirect_handler(self):
|
|
# it should trace the built-in RedirectHandler
|
|
response = self.fetch('/redirect/')
|
|
assert 200 == response.code
|
|
|
|
# we trace two different calls: the RedirectHandler and the SuccessHandler
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 2 == len(traces)
|
|
assert 1 == len(traces[0])
|
|
assert 1 == len(traces[1])
|
|
|
|
redirect_span = traces[0][0]
|
|
assert 'tornado-web' == redirect_span.service
|
|
assert 'tornado.request' == redirect_span.name
|
|
assert 'web' == redirect_span.span_type
|
|
assert 'tornado.web.RedirectHandler' == redirect_span.resource
|
|
assert 'GET' == redirect_span.get_tag('http.method')
|
|
assert_span_http_status_code(redirect_span, 301)
|
|
assert self.get_url('/redirect/') == redirect_span.get_tag(http.URL)
|
|
assert 0 == redirect_span.error
|
|
|
|
success_span = traces[1][0]
|
|
assert 'tornado-web' == success_span.service
|
|
assert 'tornado.request' == success_span.name
|
|
assert 'web' == success_span.span_type
|
|
assert 'tests.contrib.tornado.web.app.SuccessHandler' == success_span.resource
|
|
assert 'GET' == success_span.get_tag('http.method')
|
|
assert_span_http_status_code(success_span, 200)
|
|
assert self.get_url('/success/') == success_span.get_tag(http.URL)
|
|
assert 0 == success_span.error
|
|
|
|
def test_static_handler(self):
|
|
# it should trace the access to static files
|
|
response = self.fetch('/statics/empty.txt')
|
|
assert 200 == response.code
|
|
assert 'Static file\n' == response.body.decode('utf-8')
|
|
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 1 == len(traces)
|
|
assert 1 == len(traces[0])
|
|
|
|
request_span = traces[0][0]
|
|
assert 'tornado-web' == request_span.service
|
|
assert 'tornado.request' == request_span.name
|
|
assert 'web' == request_span.span_type
|
|
assert 'tornado.web.StaticFileHandler' == request_span.resource
|
|
assert 'GET' == request_span.get_tag('http.method')
|
|
assert_span_http_status_code(request_span, 200)
|
|
assert self.get_url('/statics/empty.txt') == request_span.get_tag(http.URL)
|
|
assert 0 == request_span.error
|
|
|
|
def test_propagation(self):
|
|
# it should trace a handler that returns 200 with a propagated context
|
|
headers = {
|
|
'x-datadog-trace-id': '1234',
|
|
'x-datadog-parent-id': '4567',
|
|
'x-datadog-sampling-priority': '2'
|
|
}
|
|
response = self.fetch('/success/', headers=headers)
|
|
assert 200 == response.code
|
|
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 1 == len(traces)
|
|
assert 1 == len(traces[0])
|
|
|
|
request_span = traces[0][0]
|
|
|
|
# simple sanity check on the span
|
|
assert 'tornado.request' == request_span.name
|
|
assert_span_http_status_code(request_span, 200)
|
|
assert self.get_url('/success/') == request_span.get_tag(http.URL)
|
|
assert 0 == request_span.error
|
|
|
|
# check propagation
|
|
assert 1234 == request_span.trace_id
|
|
assert 4567 == request_span.parent_id
|
|
assert 2 == request_span.get_metric(SAMPLING_PRIORITY_KEY)
|
|
|
|
# Opentracing support depends on new AsyncioScopeManager
|
|
# See: https://github.com/opentracing/opentracing-python/pull/118
|
|
@pytest.mark.skipif(tornado.version_info >= (5, 0),
|
|
reason='Opentracing ScopeManager not available for Tornado >= 5')
|
|
def test_success_handler_ot(self):
|
|
"""OpenTracing version of test_success_handler."""
|
|
from opentracing.scope_managers.tornado import TornadoScopeManager
|
|
ot_tracer = init_tracer('tornado_svc', self.tracer, scope_manager=TornadoScopeManager())
|
|
|
|
with ot_tracer.start_active_span('tornado_op'):
|
|
response = self.fetch('/success/')
|
|
assert 200 == response.code
|
|
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 1 == len(traces)
|
|
assert 2 == len(traces[0])
|
|
# dd_span will start and stop before the ot_span finishes
|
|
ot_span, dd_span = traces[0]
|
|
|
|
# confirm the parenting
|
|
assert ot_span.parent_id is None
|
|
assert dd_span.parent_id == ot_span.span_id
|
|
|
|
assert ot_span.name == 'tornado_op'
|
|
assert ot_span.service == 'tornado_svc'
|
|
|
|
assert 'tornado-web' == dd_span.service
|
|
assert 'tornado.request' == dd_span.name
|
|
assert 'web' == dd_span.span_type
|
|
assert 'tests.contrib.tornado.web.app.SuccessHandler' == dd_span.resource
|
|
assert 'GET' == dd_span.get_tag('http.method')
|
|
assert_span_http_status_code(dd_span, 200)
|
|
assert self.get_url('/success/') == dd_span.get_tag(http.URL)
|
|
assert 0 == dd_span.error
|
|
|
|
|
|
class TestTornadoWebAnalyticsDefault(TornadoTestCase):
|
|
"""
|
|
Ensure that Tornado web handlers generate APM events with default settings
|
|
"""
|
|
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)):
|
|
# it should trace a handler that returns 200
|
|
response = self.fetch('/success/')
|
|
self.assertEqual(200, response.code)
|
|
|
|
self.assert_structure(
|
|
dict(name='tornado.request', metrics={ANALYTICS_SAMPLE_RATE_KEY: 1.0}),
|
|
)
|
|
|
|
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)):
|
|
# it should trace a handler that returns 200
|
|
response = self.fetch('/success/')
|
|
self.assertEqual(200, response.code)
|
|
|
|
root = self.get_root_span()
|
|
self.assertIsNone(root.get_metric(ANALYTICS_SAMPLE_RATE_KEY))
|
|
|
|
|
|
class TestTornadoWebAnalyticsOn(TornadoTestCase):
|
|
"""
|
|
Ensure that Tornado web handlers generate APM events with default settings
|
|
"""
|
|
def get_settings(self):
|
|
# distributed_tracing needs to be disabled manually
|
|
return {
|
|
'datadog_trace': {
|
|
'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)):
|
|
# it should trace a handler that returns 200
|
|
response = self.fetch('/success/')
|
|
self.assertEqual(200, response.code)
|
|
|
|
self.assert_structure(
|
|
dict(name='tornado.request', metrics={ANALYTICS_SAMPLE_RATE_KEY: 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)):
|
|
# it should trace a handler that returns 200
|
|
response = self.fetch('/success/')
|
|
self.assertEqual(200, response.code)
|
|
|
|
self.assert_structure(
|
|
dict(name='tornado.request', metrics={ANALYTICS_SAMPLE_RATE_KEY: 0.5}),
|
|
)
|
|
|
|
|
|
class TestTornadoWebAnalyticsNoRate(TornadoTestCase):
|
|
"""
|
|
Ensure that Tornado web handlers generate APM events with default settings
|
|
"""
|
|
def get_settings(self):
|
|
# distributed_tracing needs to be disabled manually
|
|
return {
|
|
'datadog_trace': {
|
|
'analytics_enabled': True,
|
|
},
|
|
}
|
|
|
|
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)):
|
|
# it should trace a handler that returns 200
|
|
response = self.fetch('/success/')
|
|
self.assertEqual(200, response.code)
|
|
|
|
self.assert_structure(
|
|
dict(name='tornado.request', metrics={ANALYTICS_SAMPLE_RATE_KEY: 1.0}),
|
|
)
|
|
|
|
|
|
class TestNoPropagationTornadoWeb(TornadoTestCase):
|
|
"""
|
|
Ensure that Tornado web handlers are properly traced and are ignoring propagated HTTP headers when disabled.
|
|
"""
|
|
def get_settings(self):
|
|
# distributed_tracing needs to be disabled manually
|
|
return {
|
|
'datadog_trace': {
|
|
'distributed_tracing': False,
|
|
},
|
|
}
|
|
|
|
def test_no_propagation(self):
|
|
# it should not propagate the HTTP context
|
|
headers = {
|
|
'x-datadog-trace-id': '1234',
|
|
'x-datadog-parent-id': '4567',
|
|
'x-datadog-sampling-priority': '2',
|
|
'x-datadog-origin': 'synthetics',
|
|
}
|
|
response = self.fetch('/success/', headers=headers)
|
|
assert 200 == response.code
|
|
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 1 == len(traces)
|
|
assert 1 == len(traces[0])
|
|
|
|
request_span = traces[0][0]
|
|
|
|
# simple sanity check on the span
|
|
assert 'tornado.request' == request_span.name
|
|
assert_span_http_status_code(request_span, 200)
|
|
assert self.get_url('/success/') == request_span.get_tag(http.URL)
|
|
assert 0 == request_span.error
|
|
|
|
# check non-propagation
|
|
assert request_span.trace_id != 1234
|
|
assert request_span.parent_id != 4567
|
|
assert request_span.get_metric(SAMPLING_PRIORITY_KEY) != 2
|
|
assert request_span.get_tag(ORIGIN_KEY) != 'synthetics'
|
|
|
|
|
|
class TestCustomTornadoWeb(TornadoTestCase):
|
|
"""
|
|
Ensure that Tornado web handlers are properly traced when using
|
|
a custom default handler.
|
|
"""
|
|
def get_settings(self):
|
|
return {
|
|
'default_handler_class': CustomDefaultHandler,
|
|
'default_handler_args': dict(status_code=400),
|
|
}
|
|
|
|
def test_custom_default_handler(self):
|
|
# it should trace any call that uses a custom default handler
|
|
response = self.fetch('/custom_handler/')
|
|
assert 400 == response.code
|
|
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 1 == len(traces)
|
|
assert 1 == len(traces[0])
|
|
|
|
request_span = traces[0][0]
|
|
assert 'tornado-web' == request_span.service
|
|
assert 'tornado.request' == request_span.name
|
|
assert 'web' == request_span.span_type
|
|
assert 'tests.contrib.tornado.web.app.CustomDefaultHandler' == request_span.resource
|
|
assert 'GET' == request_span.get_tag('http.method')
|
|
assert_span_http_status_code(request_span, 400)
|
|
assert self.get_url('/custom_handler/') == request_span.get_tag(http.URL)
|
|
assert 0 == request_span.error
|