mirror of
https://github.com/open-telemetry/opentelemetry-python-contrib.git
synced 2025-08-01 09:13:23 +08:00
231 lines
8.8 KiB
Python
231 lines
8.8 KiB
Python
# project
|
|
from .conf import settings
|
|
from .compat import user_is_authenticated, get_resolver
|
|
from .utils import get_request_uri
|
|
|
|
from ...constants import ANALYTICS_SAMPLE_RATE_KEY
|
|
from ...contrib import func_name
|
|
from ...ext import SpanTypes, http
|
|
from ...internal.logger import get_logger
|
|
from ...propagation.http import HTTPPropagator
|
|
from ...settings import config
|
|
|
|
# 3p
|
|
from django.core.exceptions import MiddlewareNotUsed
|
|
from django.conf import settings as django_settings
|
|
import django
|
|
|
|
try:
|
|
from django.utils.deprecation import MiddlewareMixin
|
|
|
|
MiddlewareClass = MiddlewareMixin
|
|
except ImportError:
|
|
MiddlewareClass = object
|
|
|
|
log = get_logger(__name__)
|
|
|
|
EXCEPTION_MIDDLEWARE = "ddtrace.contrib.django.TraceExceptionMiddleware"
|
|
TRACE_MIDDLEWARE = "ddtrace.contrib.django.TraceMiddleware"
|
|
MIDDLEWARE = "MIDDLEWARE"
|
|
MIDDLEWARE_CLASSES = "MIDDLEWARE_CLASSES"
|
|
|
|
# Default views list available from:
|
|
# https://github.com/django/django/blob/38e2fdadfd9952e751deed662edf4c496d238f28/django/views/defaults.py
|
|
# DEV: Django doesn't call `process_view` when falling back to one of these internal error handling views
|
|
# DEV: We only use these names when `span.resource == 'unknown'` and we have one of these status codes
|
|
_django_default_views = {
|
|
400: "django.views.defaults.bad_request",
|
|
403: "django.views.defaults.permission_denied",
|
|
404: "django.views.defaults.page_not_found",
|
|
500: "django.views.defaults.server_error",
|
|
}
|
|
|
|
|
|
def _analytics_enabled():
|
|
return (
|
|
(config.analytics_enabled and settings.ANALYTICS_ENABLED is not False) or settings.ANALYTICS_ENABLED is True
|
|
) and settings.ANALYTICS_SAMPLE_RATE is not None
|
|
|
|
|
|
def get_middleware_insertion_point():
|
|
"""Returns the attribute name and collection object for the Django middleware.
|
|
|
|
If middleware cannot be found, returns None for the middleware collection.
|
|
"""
|
|
middleware = getattr(django_settings, MIDDLEWARE, None)
|
|
# Prioritise MIDDLEWARE over ..._CLASSES, but only in 1.10 and later.
|
|
if middleware is not None and django.VERSION >= (1, 10):
|
|
return MIDDLEWARE, middleware
|
|
return MIDDLEWARE_CLASSES, getattr(django_settings, MIDDLEWARE_CLASSES, None)
|
|
|
|
|
|
def insert_trace_middleware():
|
|
middleware_attribute, middleware = get_middleware_insertion_point()
|
|
if middleware is not None and TRACE_MIDDLEWARE not in set(middleware):
|
|
setattr(django_settings, middleware_attribute, type(middleware)((TRACE_MIDDLEWARE,)) + middleware)
|
|
|
|
|
|
def remove_trace_middleware():
|
|
_, middleware = get_middleware_insertion_point()
|
|
if middleware and TRACE_MIDDLEWARE in set(middleware):
|
|
middleware.remove(TRACE_MIDDLEWARE)
|
|
|
|
|
|
def insert_exception_middleware():
|
|
middleware_attribute, middleware = get_middleware_insertion_point()
|
|
if middleware is not None and EXCEPTION_MIDDLEWARE not in set(middleware):
|
|
setattr(django_settings, middleware_attribute, middleware + type(middleware)((EXCEPTION_MIDDLEWARE,)))
|
|
|
|
|
|
def remove_exception_middleware():
|
|
_, middleware = get_middleware_insertion_point()
|
|
if middleware and EXCEPTION_MIDDLEWARE in set(middleware):
|
|
middleware.remove(EXCEPTION_MIDDLEWARE)
|
|
|
|
|
|
class InstrumentationMixin(MiddlewareClass):
|
|
"""
|
|
Useful mixin base class for tracing middlewares
|
|
"""
|
|
|
|
def __init__(self, get_response=None):
|
|
# disable the middleware if the tracer is not enabled
|
|
# or if the auto instrumentation is disabled
|
|
self.get_response = get_response
|
|
if not settings.AUTO_INSTRUMENT:
|
|
raise MiddlewareNotUsed
|
|
|
|
|
|
class TraceExceptionMiddleware(InstrumentationMixin):
|
|
"""
|
|
Middleware that traces exceptions raised
|
|
"""
|
|
|
|
def process_exception(self, request, exception):
|
|
try:
|
|
span = _get_req_span(request)
|
|
if span:
|
|
span.set_tag(http.STATUS_CODE, "500")
|
|
span.set_traceback() # will set the exception info
|
|
except Exception:
|
|
log.debug("error processing exception", exc_info=True)
|
|
|
|
|
|
class TraceMiddleware(InstrumentationMixin):
|
|
"""
|
|
Middleware that traces Django requests
|
|
"""
|
|
|
|
def process_request(self, request):
|
|
tracer = settings.TRACER
|
|
if settings.DISTRIBUTED_TRACING:
|
|
propagator = HTTPPropagator()
|
|
context = propagator.extract(request.META)
|
|
# Only need to active the new context if something was propagated
|
|
if context.trace_id:
|
|
tracer.context_provider.activate(context)
|
|
try:
|
|
span = tracer.trace(
|
|
"django.request",
|
|
service=settings.DEFAULT_SERVICE,
|
|
resource="unknown", # will be filled by process view
|
|
span_type=SpanTypes.WEB,
|
|
)
|
|
|
|
# set analytics sample rate
|
|
# DEV: django is special case maintains separate configuration from config api
|
|
if _analytics_enabled() and settings.ANALYTICS_SAMPLE_RATE is not None:
|
|
span.set_tag(
|
|
ANALYTICS_SAMPLE_RATE_KEY, settings.ANALYTICS_SAMPLE_RATE,
|
|
)
|
|
|
|
# Set HTTP Request tags
|
|
span.set_tag(http.METHOD, request.method)
|
|
span.set_tag(http.URL, get_request_uri(request))
|
|
trace_query_string = settings.TRACE_QUERY_STRING
|
|
if trace_query_string is None:
|
|
trace_query_string = config.django.trace_query_string
|
|
if trace_query_string:
|
|
span.set_tag(http.QUERY_STRING, request.META["QUERY_STRING"])
|
|
_set_req_span(request, span)
|
|
except Exception:
|
|
log.debug("error tracing request", exc_info=True)
|
|
|
|
def process_view(self, request, view_func, *args, **kwargs):
|
|
span = _get_req_span(request)
|
|
if span:
|
|
span.resource = func_name(view_func)
|
|
|
|
def process_response(self, request, response):
|
|
try:
|
|
span = _get_req_span(request)
|
|
if span:
|
|
if response.status_code < 500 and span.error:
|
|
# remove any existing stack trace since it must have been
|
|
# handled appropriately
|
|
span._remove_exc_info()
|
|
|
|
# If `process_view` was not called, try to determine the correct `span.resource` to set
|
|
# DEV: `process_view` won't get called if a middle `process_request` returns an HttpResponse
|
|
# DEV: `process_view` won't get called when internal error handlers are used (e.g. for 404 responses)
|
|
if span.resource == "unknown":
|
|
try:
|
|
# Attempt to lookup the view function from the url resolver
|
|
# https://github.com/django/django/blob/38e2fdadfd9952e751deed662edf4c496d238f28/django/core/handlers/base.py#L104-L113 # noqa
|
|
urlconf = None
|
|
if hasattr(request, "urlconf"):
|
|
urlconf = request.urlconf
|
|
resolver = get_resolver(urlconf)
|
|
|
|
# Try to resolve the Django view for handling this request
|
|
if getattr(request, "request_match", None):
|
|
request_match = request.request_match
|
|
else:
|
|
# This may raise a `django.urls.exceptions.Resolver404` exception
|
|
request_match = resolver.resolve(request.path_info)
|
|
span.resource = func_name(request_match.func)
|
|
except Exception:
|
|
log.debug("error determining request view function", exc_info=True)
|
|
|
|
# If the view could not be found, try to set from a static list of
|
|
# known internal error handler views
|
|
span.resource = _django_default_views.get(response.status_code, "unknown")
|
|
|
|
span.set_tag(http.STATUS_CODE, response.status_code)
|
|
span = _set_auth_tags(span, request)
|
|
span.finish()
|
|
except Exception:
|
|
log.debug("error tracing request", exc_info=True)
|
|
finally:
|
|
return response
|
|
|
|
|
|
def _get_req_span(request):
|
|
""" Return the datadog span from the given request. """
|
|
return getattr(request, "_datadog_request_span", None)
|
|
|
|
|
|
def _set_req_span(request, span):
|
|
""" Set the datadog span on the given request. """
|
|
return setattr(request, "_datadog_request_span", span)
|
|
|
|
|
|
def _set_auth_tags(span, request):
|
|
""" Patch any available auth tags from the request onto the span. """
|
|
user = getattr(request, "user", None)
|
|
if not user:
|
|
return span
|
|
|
|
if hasattr(user, "is_authenticated"):
|
|
span.set_tag("django.user.is_authenticated", user_is_authenticated(user))
|
|
|
|
uid = getattr(user, "pk", None)
|
|
if uid:
|
|
span.set_tag("django.user.id", uid)
|
|
|
|
uname = getattr(user, "username", None)
|
|
if uname:
|
|
span.set_tag("django.user.name", uname)
|
|
|
|
return span
|