mirror of
https://github.com/open-telemetry/opentelemetry-python-contrib.git
synced 2025-07-30 21:56:07 +08:00
147 lines
5.2 KiB
Python
147 lines
5.2 KiB
Python
import asyncio
|
|
|
|
from ..asyncio import context_provider
|
|
from ...compat import stringify
|
|
from ...constants import ANALYTICS_SAMPLE_RATE_KEY
|
|
from ...ext import SpanTypes, http
|
|
from ...propagation.http import HTTPPropagator
|
|
from ...settings import config
|
|
|
|
|
|
CONFIG_KEY = 'datadog_trace'
|
|
REQUEST_CONTEXT_KEY = 'datadog_context'
|
|
REQUEST_CONFIG_KEY = '__datadog_trace_config'
|
|
REQUEST_SPAN_KEY = '__datadog_request_span'
|
|
|
|
|
|
@asyncio.coroutine
|
|
def trace_middleware(app, handler):
|
|
"""
|
|
``aiohttp`` middleware that traces the handler execution.
|
|
Because handlers are run in different tasks for each request, we attach the Context
|
|
instance both to the Task and to the Request objects. In this way:
|
|
|
|
* the Task is used by the internal automatic instrumentation
|
|
* the ``Context`` attached to the request can be freely used in the application code
|
|
"""
|
|
@asyncio.coroutine
|
|
def attach_context(request):
|
|
# application configs
|
|
tracer = app[CONFIG_KEY]['tracer']
|
|
service = app[CONFIG_KEY]['service']
|
|
distributed_tracing = app[CONFIG_KEY]['distributed_tracing_enabled']
|
|
|
|
# Create a new context based on the propagated information.
|
|
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)
|
|
|
|
# trace the handler
|
|
request_span = tracer.trace(
|
|
'aiohttp.request',
|
|
service=service,
|
|
span_type=SpanTypes.WEB,
|
|
)
|
|
|
|
# Configure trace search sample rate
|
|
# DEV: aiohttp is special case maintains separate configuration from config api
|
|
analytics_enabled = app[CONFIG_KEY]['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,
|
|
app[CONFIG_KEY].get('analytics_sample_rate', True)
|
|
)
|
|
|
|
# attach the context and the root span to the request; the Context
|
|
# may be freely used by the application code
|
|
request[REQUEST_CONTEXT_KEY] = request_span.context
|
|
request[REQUEST_SPAN_KEY] = request_span
|
|
request[REQUEST_CONFIG_KEY] = app[CONFIG_KEY]
|
|
try:
|
|
response = yield from handler(request)
|
|
return response
|
|
except Exception:
|
|
request_span.set_traceback()
|
|
raise
|
|
return attach_context
|
|
|
|
|
|
@asyncio.coroutine
|
|
def on_prepare(request, response):
|
|
"""
|
|
The on_prepare signal is used to close the request span that is created during
|
|
the trace middleware execution.
|
|
"""
|
|
# safe-guard: discard if we don't have a request span
|
|
request_span = request.get(REQUEST_SPAN_KEY, None)
|
|
if not request_span:
|
|
return
|
|
|
|
# default resource name
|
|
resource = stringify(response.status)
|
|
|
|
if request.match_info.route.resource:
|
|
# collect the resource name based on http resource type
|
|
res_info = request.match_info.route.resource.get_info()
|
|
|
|
if res_info.get('path'):
|
|
resource = res_info.get('path')
|
|
elif res_info.get('formatter'):
|
|
resource = res_info.get('formatter')
|
|
elif res_info.get('prefix'):
|
|
resource = res_info.get('prefix')
|
|
|
|
# prefix the resource name by the http method
|
|
resource = '{} {}'.format(request.method, resource)
|
|
|
|
if 500 <= response.status < 600:
|
|
request_span.error = 1
|
|
|
|
request_span.resource = resource
|
|
request_span.set_tag('http.method', request.method)
|
|
request_span.set_tag('http.status_code', response.status)
|
|
request_span.set_tag(http.URL, request.url.with_query(None))
|
|
# DEV: aiohttp is special case maintains separate configuration from config api
|
|
trace_query_string = request[REQUEST_CONFIG_KEY].get('trace_query_string')
|
|
if trace_query_string is None:
|
|
trace_query_string = config._http.trace_query_string
|
|
if trace_query_string:
|
|
request_span.set_tag(http.QUERY_STRING, request.query_string)
|
|
request_span.finish()
|
|
|
|
|
|
def trace_app(app, tracer, service='aiohttp-web'):
|
|
"""
|
|
Tracing function that patches the ``aiohttp`` application so that it will be
|
|
traced using the given ``tracer``.
|
|
|
|
:param app: aiohttp application to trace
|
|
:param tracer: tracer instance to use
|
|
:param service: service name of tracer
|
|
"""
|
|
|
|
# safe-guard: don't trace an application twice
|
|
if getattr(app, '__datadog_trace', False):
|
|
return
|
|
setattr(app, '__datadog_trace', True)
|
|
|
|
# configure datadog settings
|
|
app[CONFIG_KEY] = {
|
|
'tracer': tracer,
|
|
'service': service,
|
|
'distributed_tracing_enabled': True,
|
|
'analytics_enabled': None,
|
|
'analytics_sample_rate': 1.0,
|
|
}
|
|
|
|
# the tracer must work with asynchronous Context propagation
|
|
tracer.configure(context_provider=context_provider)
|
|
|
|
# add the async tracer middleware as a first middleware
|
|
# and be sure that the on_prepare signal is the last one
|
|
app.middlewares.insert(0, trace_middleware)
|
|
app.on_response_prepare.append(on_prepare)
|