mirror of
https://github.com/open-telemetry/opentelemetry-python-contrib.git
synced 2025-07-31 14:11:50 +08:00
491 lines
18 KiB
Python
491 lines
18 KiB
Python
import asyncio
|
|
|
|
from aiohttp.test_utils import unittest_run_loop
|
|
|
|
from ddtrace.contrib.aiohttp.middlewares import trace_app, trace_middleware, CONFIG_KEY
|
|
from ddtrace.ext import http
|
|
from ddtrace.sampler import RateSampler
|
|
from ddtrace.constants import SAMPLING_PRIORITY_KEY, ANALYTICS_SAMPLE_RATE_KEY
|
|
|
|
from opentracing.scope_managers.asyncio import AsyncioScopeManager
|
|
from tests.opentracer.utils import init_tracer
|
|
from .utils import TraceTestCase
|
|
from .app.web import setup_app, noop_middleware
|
|
from ...utils import assert_span_http_status_code
|
|
|
|
|
|
class TestTraceMiddleware(TraceTestCase):
|
|
"""
|
|
Ensures that the trace Middleware creates root spans at
|
|
the beginning of a request.
|
|
"""
|
|
def enable_tracing(self):
|
|
trace_app(self.app, self.tracer)
|
|
|
|
@unittest_run_loop
|
|
@asyncio.coroutine
|
|
def test_handler(self):
|
|
# it should create a root span when there is a handler hit
|
|
# with the proper tags
|
|
request = yield from self.client.request('GET', '/')
|
|
assert 200 == request.status
|
|
text = yield from request.text()
|
|
assert 'What\'s tracing?' == text
|
|
# the trace is created
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 1 == len(traces)
|
|
assert 1 == len(traces[0])
|
|
span = traces[0][0]
|
|
# with the right fields
|
|
assert 'aiohttp.request' == span.name
|
|
assert 'aiohttp-web' == span.service
|
|
assert 'web' == span.span_type
|
|
assert 'GET /' == span.resource
|
|
assert str(self.client.make_url('/')) == span.get_tag(http.URL)
|
|
assert 'GET' == span.get_tag('http.method')
|
|
assert_span_http_status_code(span, 200)
|
|
assert 0 == span.error
|
|
|
|
@asyncio.coroutine
|
|
def _test_param_handler(self, query_string=''):
|
|
if query_string:
|
|
fqs = '?' + query_string
|
|
else:
|
|
fqs = ''
|
|
# it should manage properly handlers with params
|
|
request = yield from self.client.request('GET', '/echo/team' + fqs)
|
|
assert 200 == request.status
|
|
text = yield from request.text()
|
|
assert 'Hello team' == text
|
|
# the trace is created
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 1 == len(traces)
|
|
assert 1 == len(traces[0])
|
|
span = traces[0][0]
|
|
# with the right fields
|
|
assert 'GET /echo/{name}' == span.resource
|
|
assert str(self.client.make_url('/echo/team')) == span.get_tag(http.URL)
|
|
assert_span_http_status_code(span, 200)
|
|
if self.app[CONFIG_KEY].get('trace_query_string'):
|
|
assert query_string == span.get_tag(http.QUERY_STRING)
|
|
else:
|
|
assert http.QUERY_STRING not in span.meta
|
|
|
|
@unittest_run_loop
|
|
def test_param_handler(self):
|
|
return self._test_param_handler()
|
|
|
|
@unittest_run_loop
|
|
def test_query_string(self):
|
|
return self._test_param_handler('foo=bar')
|
|
|
|
@unittest_run_loop
|
|
def test_query_string_duplicate_keys(self):
|
|
return self._test_param_handler('foo=bar&foo=baz&x=y')
|
|
|
|
@unittest_run_loop
|
|
def test_param_handler_trace(self):
|
|
self.app[CONFIG_KEY]['trace_query_string'] = True
|
|
return self._test_param_handler()
|
|
|
|
@unittest_run_loop
|
|
def test_query_string_trace(self):
|
|
self.app[CONFIG_KEY]['trace_query_string'] = True
|
|
return self._test_param_handler('foo=bar')
|
|
|
|
@unittest_run_loop
|
|
def test_query_string_duplicate_keys_trace(self):
|
|
self.app[CONFIG_KEY]['trace_query_string'] = True
|
|
return self._test_param_handler('foo=bar&foo=baz&x=y')
|
|
|
|
@unittest_run_loop
|
|
@asyncio.coroutine
|
|
def test_404_handler(self):
|
|
# it should not pollute the resource space
|
|
request = yield from self.client.request('GET', '/404/not_found')
|
|
assert 404 == request.status
|
|
# the trace is created
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 1 == len(traces)
|
|
assert 1 == len(traces[0])
|
|
span = traces[0][0]
|
|
# with the right fields
|
|
assert '404' == span.resource
|
|
assert str(self.client.make_url('/404/not_found')) == span.get_tag(http.URL)
|
|
assert 'GET' == span.get_tag('http.method')
|
|
assert_span_http_status_code(span, 404)
|
|
|
|
@unittest_run_loop
|
|
@asyncio.coroutine
|
|
def test_server_error(self):
|
|
"""
|
|
When a server error occurs (uncaught exception)
|
|
The span should be flagged as an error
|
|
"""
|
|
request = yield from self.client.request('GET', '/uncaught_server_error')
|
|
assert request.status == 500
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert len(traces) == 1
|
|
assert len(traces[0]) == 1
|
|
span = traces[0][0]
|
|
assert span.get_tag('http.method') == 'GET'
|
|
assert_span_http_status_code(span, 500)
|
|
assert span.error == 1
|
|
|
|
@unittest_run_loop
|
|
@asyncio.coroutine
|
|
def test_500_response_code(self):
|
|
"""
|
|
When a 5XX response code is returned
|
|
The span should be flagged as an error
|
|
"""
|
|
request = yield from self.client.request('GET', '/caught_server_error')
|
|
assert request.status == 503
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert len(traces) == 1
|
|
assert len(traces[0]) == 1
|
|
span = traces[0][0]
|
|
assert span.get_tag('http.method') == 'GET'
|
|
assert_span_http_status_code(span, 503)
|
|
assert span.error == 1
|
|
|
|
@unittest_run_loop
|
|
@asyncio.coroutine
|
|
def test_coroutine_chaining(self):
|
|
# it should create a trace with multiple spans
|
|
request = yield from self.client.request('GET', '/chaining/')
|
|
assert 200 == request.status
|
|
text = yield from request.text()
|
|
assert 'OK' == text
|
|
# the trace is created
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 1 == len(traces)
|
|
assert 3 == len(traces[0])
|
|
root = traces[0][0]
|
|
handler = traces[0][1]
|
|
coroutine = traces[0][2]
|
|
# root span created in the middleware
|
|
assert 'aiohttp.request' == root.name
|
|
assert 'GET /chaining/' == root.resource
|
|
assert str(self.client.make_url('/chaining/')) == root.get_tag(http.URL)
|
|
assert 'GET' == root.get_tag('http.method')
|
|
assert_span_http_status_code(root, 200)
|
|
# span created in the coroutine_chaining handler
|
|
assert 'aiohttp.coro_1' == handler.name
|
|
assert root.span_id == handler.parent_id
|
|
assert root.trace_id == handler.trace_id
|
|
# span created in the coro_2 handler
|
|
assert 'aiohttp.coro_2' == coroutine.name
|
|
assert handler.span_id == coroutine.parent_id
|
|
assert root.trace_id == coroutine.trace_id
|
|
|
|
@unittest_run_loop
|
|
@asyncio.coroutine
|
|
def test_static_handler(self):
|
|
# it should create a trace with multiple spans
|
|
request = yield from self.client.request('GET', '/statics/empty.txt')
|
|
assert 200 == request.status
|
|
text = yield from request.text()
|
|
assert 'Static file\n' == text
|
|
# the trace is created
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 1 == len(traces)
|
|
assert 1 == len(traces[0])
|
|
span = traces[0][0]
|
|
# root span created in the middleware
|
|
assert 'aiohttp.request' == span.name
|
|
assert 'GET /statics' == span.resource
|
|
assert str(self.client.make_url('/statics/empty.txt')) == span.get_tag(http.URL)
|
|
assert 'GET' == span.get_tag('http.method')
|
|
assert_span_http_status_code(span, 200)
|
|
|
|
@unittest_run_loop
|
|
@asyncio.coroutine
|
|
def test_middleware_applied_twice(self):
|
|
# it should be idempotent
|
|
app = setup_app(self.app.loop)
|
|
# the middleware is not present
|
|
assert 1 == len(app.middlewares)
|
|
assert noop_middleware == app.middlewares[0]
|
|
# the middleware is present (with the noop middleware)
|
|
trace_app(app, self.tracer)
|
|
assert 2 == len(app.middlewares)
|
|
# applying the middleware twice doesn't add it again
|
|
trace_app(app, self.tracer)
|
|
assert 2 == len(app.middlewares)
|
|
# and the middleware is always the first
|
|
assert trace_middleware == app.middlewares[0]
|
|
assert noop_middleware == app.middlewares[1]
|
|
|
|
@unittest_run_loop
|
|
@asyncio.coroutine
|
|
def test_exception(self):
|
|
request = yield from self.client.request('GET', '/exception')
|
|
assert 500 == request.status
|
|
yield from request.text()
|
|
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 1 == len(traces)
|
|
spans = traces[0]
|
|
assert 1 == len(spans)
|
|
span = spans[0]
|
|
assert 1 == span.error
|
|
assert 'GET /exception' == span.resource
|
|
assert 'error' == span.get_tag('error.msg')
|
|
assert 'Exception: error' in span.get_tag('error.stack')
|
|
|
|
@unittest_run_loop
|
|
@asyncio.coroutine
|
|
def test_async_exception(self):
|
|
request = yield from self.client.request('GET', '/async_exception')
|
|
assert 500 == request.status
|
|
yield from request.text()
|
|
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 1 == len(traces)
|
|
spans = traces[0]
|
|
assert 1 == len(spans)
|
|
span = spans[0]
|
|
assert 1 == span.error
|
|
assert 'GET /async_exception' == span.resource
|
|
assert 'error' == span.get_tag('error.msg')
|
|
assert 'Exception: error' in span.get_tag('error.stack')
|
|
|
|
@unittest_run_loop
|
|
@asyncio.coroutine
|
|
def test_wrapped_coroutine(self):
|
|
request = yield from self.client.request('GET', '/wrapped_coroutine')
|
|
assert 200 == request.status
|
|
text = yield from request.text()
|
|
assert 'OK' == text
|
|
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 1 == len(traces)
|
|
spans = traces[0]
|
|
assert 2 == len(spans)
|
|
span = spans[0]
|
|
assert 'GET /wrapped_coroutine' == span.resource
|
|
span = spans[1]
|
|
assert 'nested' == span.name
|
|
assert span.duration > 0.25, 'span.duration={0}'.format(span.duration)
|
|
|
|
@unittest_run_loop
|
|
@asyncio.coroutine
|
|
def test_distributed_tracing(self):
|
|
# distributed tracing is enabled by default
|
|
tracing_headers = {
|
|
'x-datadog-trace-id': '100',
|
|
'x-datadog-parent-id': '42',
|
|
}
|
|
|
|
request = yield from self.client.request('GET', '/', headers=tracing_headers)
|
|
assert 200 == request.status
|
|
text = yield from request.text()
|
|
assert "What's tracing?" == text
|
|
# the trace is created
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 1 == len(traces)
|
|
assert 1 == len(traces[0])
|
|
span = traces[0][0]
|
|
# with the right trace_id and parent_id
|
|
assert span.trace_id == 100
|
|
assert span.parent_id == 42
|
|
assert span.get_metric(SAMPLING_PRIORITY_KEY) is None
|
|
|
|
@unittest_run_loop
|
|
@asyncio.coroutine
|
|
def test_distributed_tracing_with_sampling_true(self):
|
|
self.tracer.priority_sampler = RateSampler(0.1)
|
|
|
|
tracing_headers = {
|
|
'x-datadog-trace-id': '100',
|
|
'x-datadog-parent-id': '42',
|
|
'x-datadog-sampling-priority': '1',
|
|
}
|
|
|
|
request = yield from self.client.request('GET', '/', headers=tracing_headers)
|
|
assert 200 == request.status
|
|
text = yield from request.text()
|
|
assert "What's tracing?" == text
|
|
# the trace is created
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 1 == len(traces)
|
|
assert 1 == len(traces[0])
|
|
span = traces[0][0]
|
|
# with the right trace_id and parent_id
|
|
assert 100 == span.trace_id
|
|
assert 42 == span.parent_id
|
|
assert 1 == span.get_metric(SAMPLING_PRIORITY_KEY)
|
|
|
|
@unittest_run_loop
|
|
@asyncio.coroutine
|
|
def test_distributed_tracing_with_sampling_false(self):
|
|
self.tracer.priority_sampler = RateSampler(0.9)
|
|
|
|
tracing_headers = {
|
|
'x-datadog-trace-id': '100',
|
|
'x-datadog-parent-id': '42',
|
|
'x-datadog-sampling-priority': '0',
|
|
}
|
|
|
|
request = yield from self.client.request('GET', '/', headers=tracing_headers)
|
|
assert 200 == request.status
|
|
text = yield from request.text()
|
|
assert "What's tracing?" == text
|
|
# the trace is created
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 1 == len(traces)
|
|
assert 1 == len(traces[0])
|
|
span = traces[0][0]
|
|
# with the right trace_id and parent_id
|
|
assert 100 == span.trace_id
|
|
assert 42 == span.parent_id
|
|
assert 0 == span.get_metric(SAMPLING_PRIORITY_KEY)
|
|
|
|
@unittest_run_loop
|
|
@asyncio.coroutine
|
|
def test_distributed_tracing_disabled(self):
|
|
# pass headers for distributed tracing
|
|
self.app['datadog_trace']['distributed_tracing_enabled'] = False
|
|
tracing_headers = {
|
|
'x-datadog-trace-id': '100',
|
|
'x-datadog-parent-id': '42',
|
|
}
|
|
|
|
request = yield from self.client.request('GET', '/', headers=tracing_headers)
|
|
assert 200 == request.status
|
|
text = yield from request.text()
|
|
assert "What's tracing?" == text
|
|
# the trace is created
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 1 == len(traces)
|
|
assert 1 == len(traces[0])
|
|
span = traces[0][0]
|
|
# distributed tracing must be ignored by default
|
|
assert span.trace_id != 100
|
|
assert span.parent_id != 42
|
|
|
|
@unittest_run_loop
|
|
@asyncio.coroutine
|
|
def test_distributed_tracing_sub_span(self):
|
|
self.tracer.priority_sampler = RateSampler(1.0)
|
|
|
|
# activate distributed tracing
|
|
tracing_headers = {
|
|
'x-datadog-trace-id': '100',
|
|
'x-datadog-parent-id': '42',
|
|
'x-datadog-sampling-priority': '0',
|
|
}
|
|
|
|
request = yield from self.client.request('GET', '/sub_span', headers=tracing_headers)
|
|
assert 200 == request.status
|
|
text = yield from request.text()
|
|
assert 'OK' == text
|
|
# the trace is created
|
|
traces = self.tracer.writer.pop_traces()
|
|
assert 1 == len(traces)
|
|
assert 2 == len(traces[0])
|
|
span, sub_span = traces[0][0], traces[0][1]
|
|
# with the right trace_id and parent_id
|
|
assert 100 == span.trace_id
|
|
assert 42 == span.parent_id
|
|
assert 0 == span.get_metric(SAMPLING_PRIORITY_KEY)
|
|
# check parenting is OK with custom sub-span created within server code
|
|
assert 100 == sub_span.trace_id
|
|
assert span.span_id == sub_span.parent_id
|
|
assert sub_span.get_metric(SAMPLING_PRIORITY_KEY) is None
|
|
|
|
def _assert_200_parenting(self, traces):
|
|
"""Helper to assert parenting when handling aiohttp requests.
|
|
|
|
This is used to ensure that parenting is consistent between Datadog
|
|
and OpenTracing implementations of tracing.
|
|
"""
|
|
assert 2 == len(traces)
|
|
assert 1 == len(traces[0])
|
|
|
|
# the inner span will be the first trace since it completes before the
|
|
# outer span does
|
|
inner_span = traces[0][0]
|
|
outer_span = traces[1][0]
|
|
|
|
# confirm the parenting
|
|
assert outer_span.parent_id is None
|
|
assert inner_span.parent_id is None
|
|
|
|
assert outer_span.name == 'aiohttp_op'
|
|
|
|
# with the right fields
|
|
assert 'aiohttp.request' == inner_span.name
|
|
assert 'aiohttp-web' == inner_span.service
|
|
assert 'web' == inner_span.span_type
|
|
assert 'GET /' == inner_span.resource
|
|
assert str(self.client.make_url('/')) == inner_span.get_tag(http.URL)
|
|
assert 'GET' == inner_span.get_tag('http.method')
|
|
assert_span_http_status_code(inner_span, 200)
|
|
assert 0 == inner_span.error
|
|
|
|
@unittest_run_loop
|
|
@asyncio.coroutine
|
|
def test_parenting_200_dd(self):
|
|
with self.tracer.trace('aiohttp_op'):
|
|
request = yield from self.client.request('GET', '/')
|
|
assert 200 == request.status
|
|
text = yield from request.text()
|
|
|
|
assert "What's tracing?" == text
|
|
traces = self.tracer.writer.pop_traces()
|
|
self._assert_200_parenting(traces)
|
|
|
|
@unittest_run_loop
|
|
@asyncio.coroutine
|
|
def test_parenting_200_ot(self):
|
|
"""OpenTracing version of test_handler."""
|
|
ot_tracer = init_tracer('aiohttp_svc', self.tracer, scope_manager=AsyncioScopeManager())
|
|
|
|
with ot_tracer.start_active_span('aiohttp_op'):
|
|
request = yield from self.client.request('GET', '/')
|
|
assert 200 == request.status
|
|
text = yield from request.text()
|
|
|
|
assert "What's tracing?" == text
|
|
traces = self.tracer.writer.pop_traces()
|
|
self._assert_200_parenting(traces)
|
|
|
|
@unittest_run_loop
|
|
@asyncio.coroutine
|
|
def test_analytics_integration_enabled(self):
|
|
""" Check trace has analytics sample rate set """
|
|
self.app['datadog_trace']['analytics_enabled'] = True
|
|
self.app['datadog_trace']['analytics_sample_rate'] = 0.5
|
|
request = yield from self.client.request('GET', '/template/')
|
|
yield from request.text()
|
|
|
|
# Assert root span sets the appropriate metric
|
|
self.assert_structure(
|
|
dict(name='aiohttp.request', metrics={ANALYTICS_SAMPLE_RATE_KEY: 0.5})
|
|
)
|
|
|
|
@unittest_run_loop
|
|
@asyncio.coroutine
|
|
def test_analytics_integration_default(self):
|
|
""" Check trace has analytics sample rate set """
|
|
request = yield from self.client.request('GET', '/template/')
|
|
yield from request.text()
|
|
|
|
# Assert root span does not have the appropriate metric
|
|
root = self.get_root_span()
|
|
self.assertIsNone(root.get_metric(ANALYTICS_SAMPLE_RATE_KEY))
|
|
|
|
@unittest_run_loop
|
|
@asyncio.coroutine
|
|
def test_analytics_integration_disabled(self):
|
|
""" Check trace has analytics sample rate set """
|
|
self.app['datadog_trace']['analytics_enabled'] = False
|
|
request = yield from self.client.request('GET', '/template/')
|
|
yield from request.text()
|
|
|
|
# Assert root span does not have the appropriate metric
|
|
root = self.get_root_span()
|
|
self.assertIsNone(root.get_metric(ANALYTICS_SAMPLE_RATE_KEY))
|