120 lines
4.7 KiB
Python
120 lines
4.7 KiB
Python
import pyramid.renderers
|
|
from pyramid.settings import asbool
|
|
from pyramid.httpexceptions import HTTPException
|
|
from ddtrace.vendor import wrapt
|
|
|
|
# project
|
|
import ddtrace
|
|
from ...constants import ANALYTICS_SAMPLE_RATE_KEY
|
|
from ...ext import SpanTypes, http
|
|
from ...internal.logger import get_logger
|
|
from ...propagation.http import HTTPPropagator
|
|
from ...settings import config
|
|
from .constants import (
|
|
SETTINGS_TRACER,
|
|
SETTINGS_SERVICE,
|
|
SETTINGS_TRACE_ENABLED,
|
|
SETTINGS_DISTRIBUTED_TRACING,
|
|
SETTINGS_ANALYTICS_ENABLED,
|
|
SETTINGS_ANALYTICS_SAMPLE_RATE,
|
|
)
|
|
|
|
|
|
log = get_logger(__name__)
|
|
|
|
DD_TWEEN_NAME = 'ddtrace.contrib.pyramid:trace_tween_factory'
|
|
DD_SPAN = '_datadog_span'
|
|
|
|
|
|
def trace_pyramid(config):
|
|
config.include('ddtrace.contrib.pyramid')
|
|
|
|
|
|
def includeme(config):
|
|
# Add our tween just before the default exception handler
|
|
config.add_tween(DD_TWEEN_NAME, over=pyramid.tweens.EXCVIEW)
|
|
# ensure we only patch the renderer once.
|
|
if not isinstance(pyramid.renderers.RendererHelper.render, wrapt.ObjectProxy):
|
|
wrapt.wrap_function_wrapper('pyramid.renderers', 'RendererHelper.render', trace_render)
|
|
|
|
|
|
def trace_render(func, instance, args, kwargs):
|
|
# If the request is not traced, we do not trace
|
|
request = kwargs.get('request', {})
|
|
if not request:
|
|
log.debug('No request passed to render, will not be traced')
|
|
return func(*args, **kwargs)
|
|
span = getattr(request, DD_SPAN, None)
|
|
if not span:
|
|
log.debug('No span found in request, will not be traced')
|
|
return func(*args, **kwargs)
|
|
|
|
with span.tracer.trace('pyramid.render', span_type=SpanTypes.TEMPLATE) as span:
|
|
return func(*args, **kwargs)
|
|
|
|
|
|
def trace_tween_factory(handler, registry):
|
|
# configuration
|
|
settings = registry.settings
|
|
service = settings.get(SETTINGS_SERVICE) or 'pyramid'
|
|
tracer = settings.get(SETTINGS_TRACER) or ddtrace.tracer
|
|
enabled = asbool(settings.get(SETTINGS_TRACE_ENABLED, tracer.enabled))
|
|
distributed_tracing = asbool(settings.get(SETTINGS_DISTRIBUTED_TRACING, True))
|
|
|
|
if enabled:
|
|
# make a request tracing function
|
|
def trace_tween(request):
|
|
if distributed_tracing:
|
|
propagator = HTTPPropagator()
|
|
context = propagator.extract(request.headers)
|
|
# only need to active the new context if something was propagated
|
|
if context.trace_id:
|
|
tracer.context_provider.activate(context)
|
|
with tracer.trace('pyramid.request', service=service, resource='404', span_type=SpanTypes.WEB) as span:
|
|
# Configure trace search sample rate
|
|
# DEV: pyramid is special case maintains separate configuration from config api
|
|
analytics_enabled = settings.get(SETTINGS_ANALYTICS_ENABLED)
|
|
|
|
if (
|
|
config.analytics_enabled and analytics_enabled is not False
|
|
) or analytics_enabled is True:
|
|
span.set_tag(
|
|
ANALYTICS_SAMPLE_RATE_KEY,
|
|
settings.get(SETTINGS_ANALYTICS_SAMPLE_RATE, True)
|
|
)
|
|
|
|
setattr(request, DD_SPAN, span) # used to find the tracer in templates
|
|
response = None
|
|
try:
|
|
response = handler(request)
|
|
except HTTPException as e:
|
|
# If the exception is a pyramid HTTPException,
|
|
# that's still valuable information that isn't necessarily
|
|
# a 500. For instance, HTTPFound is a 302.
|
|
# As described in docs, Pyramid exceptions are all valid
|
|
# response types
|
|
response = e
|
|
raise
|
|
except BaseException:
|
|
span.set_tag(http.STATUS_CODE, 500)
|
|
raise
|
|
finally:
|
|
# set request tags
|
|
span.set_tag(http.URL, request.path_url)
|
|
span.set_tag(http.METHOD, request.method)
|
|
if config.pyramid.trace_query_string:
|
|
span.set_tag(http.QUERY_STRING, request.query_string)
|
|
if request.matched_route:
|
|
span.resource = '{} {}'.format(request.method, request.matched_route.name)
|
|
span.set_tag('pyramid.route.name', request.matched_route.name)
|
|
# set response tags
|
|
if response:
|
|
span.set_tag(http.STATUS_CODE, response.status_code)
|
|
if 500 <= response.status_code < 600:
|
|
span.error = 1
|
|
return response
|
|
return trace_tween
|
|
|
|
# if timing support is not enabled, return the original handler
|
|
return handler
|