Files
2020-04-08 10:39:44 -07:00

106 lines
4.2 KiB
Python

from tornado.web import HTTPError
from .constants import CONFIG_KEY, REQUEST_CONTEXT_KEY, REQUEST_SPAN_KEY
from .stack_context import TracerStackContext
from ...constants import ANALYTICS_SAMPLE_RATE_KEY
from ...ext import SpanTypes, http
from ...propagation.http import HTTPPropagator
from ...settings import config
def execute(func, handler, args, kwargs):
"""
Wrap the handler execute method so that the entire request is within the same
``TracerStackContext``. This simplifies users code when the automatic ``Context``
retrieval is used via ``Tracer.trace()`` method.
"""
# retrieve tracing settings
settings = handler.settings[CONFIG_KEY]
tracer = settings['tracer']
service = settings['default_service']
distributed_tracing = settings['distributed_tracing']
with TracerStackContext():
# attach the context to the request
setattr(handler.request, REQUEST_CONTEXT_KEY, tracer.get_call_context())
# Read and use propagated context from HTTP headers
if distributed_tracing:
propagator = HTTPPropagator()
context = propagator.extract(handler.request.headers)
if context.trace_id:
tracer.context_provider.activate(context)
# store the request span in the request so that it can be used later
request_span = tracer.trace(
'tornado.request',
service=service,
span_type=SpanTypes.WEB
)
# set analytics sample rate
# DEV: tornado is special case maintains separate configuration from config api
analytics_enabled = settings['analytics_enabled']
if (config.analytics_enabled and analytics_enabled is not False) or analytics_enabled is True:
request_span.set_tag(
ANALYTICS_SAMPLE_RATE_KEY,
settings.get('analytics_sample_rate', True)
)
setattr(handler.request, REQUEST_SPAN_KEY, request_span)
return func(*args, **kwargs)
def on_finish(func, handler, args, kwargs):
"""
Wrap the ``RequestHandler.on_finish`` method. This is the last executed method
after the response has been sent, and it's used to retrieve and close the
current request span (if available).
"""
request = handler.request
request_span = getattr(request, REQUEST_SPAN_KEY, None)
if request_span:
# use the class name as a resource; if an handler is not available, the
# default handler class will be used so we don't pollute the resource
# space here
klass = handler.__class__
request_span.resource = '{}.{}'.format(klass.__module__, klass.__name__)
request_span.set_tag('http.method', request.method)
request_span.set_tag('http.status_code', handler.get_status())
request_span.set_tag(http.URL, request.full_url().rsplit('?', 1)[0])
if config.tornado.trace_query_string:
request_span.set_tag(http.QUERY_STRING, request.query)
request_span.finish()
return func(*args, **kwargs)
def log_exception(func, handler, args, kwargs):
"""
Wrap the ``RequestHandler.log_exception``. This method is called when an
Exception is not handled in the user code. In this case, we save the exception
in the current active span. If the Tornado ``Finish`` exception is raised, this wrapper
will not be called because ``Finish`` is not an exception.
"""
# safe-guard: expected arguments -> log_exception(self, typ, value, tb)
value = args[1] if len(args) == 3 else None
if not value:
return func(*args, **kwargs)
# retrieve the current span
tracer = handler.settings[CONFIG_KEY]['tracer']
current_span = tracer.current_span()
if isinstance(value, HTTPError):
# Tornado uses HTTPError exceptions to stop and return a status code that
# is not a 2xx. In this case we want to check the status code to be sure that
# only 5xx are traced as errors, while any other HTTPError exception is handled as
# usual.
if 500 <= value.status_code <= 599:
current_span.set_exc_info(*args)
else:
# any other uncaught exception should be reported as error
current_span.set_exc_info(*args)
return func(*args, **kwargs)