Exemple #1
0
def _instrument_view(django, view):
    """Helper to wrap Django views."""
    # All views should be callable, double check before doing anything
    if not callable(view):
        return view

    # Patch view HTTP methods and lifecycle methods
    http_method_names = getattr(view, "http_method_names",
                                ("get", "delete", "post", "options", "head"))
    lifecycle_methods = ("setup", "dispatch", "http_method_not_allowed")
    for name in list(http_method_names) + list(lifecycle_methods):
        try:
            func = getattr(view, name, None)
            if not func or isinstance(func, wrapt.ObjectProxy):
                continue

            resource = "{0}.{1}".format(func_name(view), name)
            op_name = "django.view.{0}".format(name)
            trace_utils.wrap(
                view, name, traced_func(django,
                                        name=op_name,
                                        resource=resource))
        except Exception:
            log.debug("Failed to instrument Django view %r function %s",
                      view,
                      name,
                      exc_info=True)

    # Patch response methods
    response_cls = getattr(view, "response_class", None)
    if response_cls:
        methods = ("render", )
        for name in methods:
            try:
                func = getattr(response_cls, name, None)
                # Do not wrap if the method does not exist or is already wrapped
                if not func or isinstance(func, wrapt.ObjectProxy):
                    continue

                resource = "{0}.{1}".format(func_name(response_cls), name)
                op_name = "django.response.{0}".format(name)
                trace_utils.wrap(
                    response_cls, name,
                    traced_func(django, name=op_name, resource=resource))
            except Exception:
                log.debug(
                    "Failed to instrument Django response %r function %s",
                    response_cls,
                    name,
                    exc_info=True)

    # If the view itself is not wrapped, wrap it
    if not isinstance(view, wrapt.ObjectProxy):
        view = wrapt.FunctionWrapper(
            view,
            traced_func(django,
                        "django.view",
                        resource=func_name(view),
                        ignored_excs=[django.http.Http404]))
    return view
Exemple #2
0
def instrument_view(django, view):
    """Helper to wrap Django views."""
    # All views should be callable, double check before doing anything
    if not callable(view) or isinstance(view, wrapt.ObjectProxy):
        return view

    # Patch view HTTP methods and lifecycle methods
    http_method_names = getattr(view, "http_method_names",
                                ("get", "delete", "post", "options", "head"))
    lifecycle_methods = ("setup", "dispatch", "http_method_not_allowed")
    for name in list(http_method_names) + list(lifecycle_methods):
        try:
            # View methods can be staticmethods
            func = getattr_static(view, name, None)
            if not func or isinstance(func, wrapt.ObjectProxy):
                continue

            resource = "{0}.{1}".format(func_name(view), name)
            op_name = "django.view.{0}".format(name)

            # Set attribute here rather than using wrapt.wrappers.wrap_function_wrapper
            # since it will not resolve attribute to staticmethods
            wrapper = wrapt.FunctionWrapper(
                func, traced_func(django, name=op_name, resource=resource))
            setattr(view, name, wrapper)
        except Exception:
            log.debug("Failed to instrument Django view %r function %s",
                      view,
                      name,
                      exc_info=True)

    # Patch response methods
    response_cls = getattr(view, "response_class", None)
    if response_cls:
        methods = ("render", )
        for name in methods:
            try:
                func = getattr(response_cls, name, None)
                # Do not wrap if the method does not exist or is already wrapped
                if not func or isinstance(func, wrapt.ObjectProxy):
                    continue

                resource = "{0}.{1}".format(func_name(response_cls), name)
                op_name = "django.response.{0}".format(name)
                wrap(response_cls, name,
                     traced_func(django, name=op_name, resource=resource))
            except Exception:
                log.debug(
                    "Failed to instrument Django response %r function %s",
                    response_cls,
                    name,
                    exc_info=True)

    # Return a wrapped version of this view
    return wrapt.FunctionWrapper(
        view, traced_func(django, "django.view", resource=func_name(view)))
Exemple #3
0
def _set_request_tags(django, span, request):
    span.set_tag("django.request.class", func_name(request))
    span.set_tag(http.METHOD, request.method)

    if django.VERSION >= (2, 2, 0):
        headers = request.headers
    else:
        headers = {}
        for header, value in request.META.items():
            name = from_wsgi_header(header)
            if name:
                headers[name] = value

    store_request_headers(headers, span, config.django)

    user = getattr(request, "user", None)
    if user is not None:
        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)

        if config.django.include_user_name:
            username = getattr(user, "username", None)
            if username:
                span.set_tag("django.user.name", username)
Exemple #4
0
def traced_template_render(django, pin, wrapped, instance, args, kwargs):
    """Instrument django.template.base.Template.render for tracing template rendering."""
    template_name = getattr(instance, "name", None)
    if template_name:
        resource = template_name
    else:
        resource = "{0}.{1}".format(func_name(instance), wrapped.__name__)

    with pin.tracer.trace("django.template.render", resource=resource, span_type=http.TEMPLATE) as span:
        if template_name:
            span.set_tag("django.template.name", template_name)
        engine = getattr(instance, "engine", None)
        if engine:
            span.set_tag("django.template.engine.class", func_name(engine))

        return wrapped(*args, **kwargs)
Exemple #5
0
def _set_request_tags(django, span, request):
    span._set_str_tag("django.request.class", func_name(request))

    user = getattr(request, "user", None)
    if user is not None:
        # Note: getattr calls to user / user_is_authenticated may result in ImproperlyConfigured exceptions from
        # Django's get_user_model():
        # https://github.com/django/django/blob/a464ead29db8bf6a27a5291cad9eb3f0f3f0472b/django/contrib/auth/__init__.py
        try:
            if hasattr(user, "is_authenticated"):
                span._set_str_tag("django.user.is_authenticated",
                                  user_is_authenticated(user))

            uid = getattr(user, "pk", None)
            if uid:
                span._set_str_tag("django.user.id", str(uid))

            if config.django.include_user_name:
                username = getattr(user, "username", None)
                if username:
                    span._set_str_tag("django.user.name", username)
        except Exception:
            log.debug(
                "Error retrieving authentication information for user %r",
                user,
                exc_info=True)
Exemple #6
0
def traced_as_view(django, pin, func, instance, args, kwargs):
    """
    Wrapper for django's View.as_view class method
    """
    try:
        instrument_view(django, instance)
    except Exception:
        log.debug("Failed to instrument Django view %r", instance, exc_info=True)
    view = func(*args, **kwargs)
    return wrapt.FunctionWrapper(view, traced_func(django, "django.view", resource=func_name(view)))
Exemple #7
0
def _set_resolver_tags(pin, span, request):
    # Default to just the HTTP method when we cannot determine a reasonable resource
    resource = request.method

    try:
        # Get resolver match result and build resource name pieces
        resolver_match = request.resolver_match
        if not resolver_match:
            # The request quite likely failed (e.g. 404) so we do the resolution anyway.
            resolver = get_resolver(getattr(request, "urlconf", None))
            resolver_match = resolver.resolve(request.path_info)
        handler = func_name(resolver_match[0])

        if config.django.use_handler_resource_format:
            resource = " ".join((request.method, handler))
        elif config.django.use_legacy_resource_format:
            resource = handler
        else:
            # In Django >= 2.2.0 we can access the original route or regex pattern
            # TODO: Validate if `resolver.pattern.regex.pattern` is available on django<2.2
            if DJANGO22:
                # Determine the resolver and resource name for this request
                route = get_django_2_route(request, resolver_match)
                if route:
                    resource = " ".join((request.method, route))
                    span._set_str_tag("http.route", route)
            else:
                resource = " ".join((request.method, handler))

        span._set_str_tag("django.view", resolver_match.view_name)
        set_tag_array(span, "django.namespace", resolver_match.namespaces)

        # Django >= 2.0.0
        if hasattr(resolver_match, "app_names"):
            set_tag_array(span, "django.app", resolver_match.app_names)

    except Resolver404:
        # Normalize all 404 requests into a single resource name
        # DEV: This is for potential cardinality issues
        resource = " ".join((request.method, "404"))
    except Exception:
        log.debug(
            "Failed to resolve request path %r with path info %r",
            request,
            getattr(request, "path_info", "not-set"),
            exc_info=True,
        )
    finally:
        # Only update the resource name if it was not explicitly set
        # by anyone during the request lifetime
        if span.resource == REQUEST_DEFAULT_RESOURCE:
            span.resource = resource
Exemple #8
0
def _before_request_tags(pin, span, request):
    # DEV: Do not set `span.resource` here, leave it as `None`
    #      until `_set_resolver_tags` so we can know if the user
    #      has explicitly set it during the request lifetime
    span.service = trace_utils.int_service(pin, config.django)
    span.span_type = SpanTypes.WEB
    span.metrics[SPAN_MEASURED_KEY] = 1

    analytics_sr = config.django.get_analytics_sample_rate(
        use_global_config=True)
    if analytics_sr is not None:
        span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, analytics_sr)

    span._set_str_tag("django.request.class", func_name(request))
Exemple #9
0
def traced_cache(django, pin, func, instance, args, kwargs):
    if not config.django.instrument_caches:
        return func(*args, **kwargs)

    # get the original function method
    with pin.tracer.trace("django.cache", span_type=SpanTypes.CACHE, service=config.django.cache_service_name) as span:
        # update the resource name and tag the cache backend
        span.resource = utils.resource_from_cache_prefix(func_name(func), instance)
        cache_backend = "{}.{}".format(instance.__module__, instance.__class__.__name__)
        span.set_tag("django.cache.backend", cache_backend)

        if args:
            keys = utils.quantize_key_values(args[0])
            span.set_tag("django.cache.key", keys)

        return func(*args, **kwargs)
Exemple #10
0
def _set_request_tags(django, span, request):
    span.set_tag("django.request.class", func_name(request))

    user = getattr(request, "user", None)
    if user is not None:
        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)

        if config.django.include_user_name:
            username = getattr(user, "username", None)
            if username:
                span.set_tag("django.user.name", username)
Exemple #11
0
def _set_request_tags(span, request):
    span.set_tag("django.request.class", func_name(request))
    span.set_tag(http.METHOD, request.method)

    user = getattr(request, "user", None)
    if user is not None:
        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)

        username = getattr(user, "username", None)
        if username:
            span.set_tag("django.user.name", username)
Exemple #12
0
def traced_get_response(django, pin, func, instance, args, kwargs):
    """Trace django.core.handlers.base.BaseHandler.get_response() (or other implementations).

    This is the main entry point for requests.

    Django requests are handled by a Handler.get_response method (inherited from base.BaseHandler).
    This method invokes the middleware chain and returns the response generated by the chain.
    """

    request = kwargs.get("request", args[0])
    if request is None:
        return func(*args, **kwargs)

    try:
        request_headers = request.META

        if config.django.distributed_tracing_enabled:
            context = propagator.extract(request_headers)
            if context.trace_id:
                pin.tracer.context_provider.activate(context)

        # Determine the resolver and resource name for this request
        resolver = get_resolver(getattr(request, "urlconf", None))

        if django.VERSION < (1, 10, 0):
            error_type_404 = django.core.urlresolvers.Resolver404
        else:
            error_type_404 = django.urls.exceptions.Resolver404

        route = None
        resolver_match = None
        resource = request.method
        try:
            # Resolve the requested url and build resource name pieces
            resolver_match = resolver.resolve(request.path_info)
            handler, _, _ = resolver_match
            handler = func_name(handler)
            urlpattern = ""
            resource_format = None

            if config.django.use_handler_resource_format:
                resource_format = "{method} {handler}"
            elif config.django.use_legacy_resource_format:
                resource_format = "{handler}"
            else:
                # In Django >= 2.2.0 we can access the original route or regex pattern
                # TODO: Validate if `resolver.pattern.regex.pattern` is available on django<2.2
                if django.VERSION >= (2, 2, 0):
                    route = utils.get_django_2_route(resolver, resolver_match)
                    resource_format = "{method} {urlpattern}"
                else:
                    resource_format = "{method} {handler}"

                if route is not None:
                    urlpattern = route

            resource = resource_format.format(method=request.method,
                                              urlpattern=urlpattern,
                                              handler=handler)

        except error_type_404:
            # Normalize all 404 requests into a single resource name
            # DEV: This is for potential cardinality issues
            resource = "{0} 404".format(request.method)
        except Exception:
            log.debug(
                "Failed to resolve request path %r with path info %r",
                request,
                getattr(request, "path_info", "not-set"),
                exc_info=True,
            )
    except Exception:
        log.debug("Failed to trace django request %r", args, exc_info=True)
        return func(*args, **kwargs)
    else:
        with pin.tracer.trace(
                "django.request",
                resource=resource,
                service=trace_utils.int_service(pin, config.django),
                span_type=SpanTypes.WEB,
        ) as span:
            analytics_sr = config.django.get_analytics_sample_rate(
                use_global_config=True)
            if analytics_sr is not None:
                span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, analytics_sr)

            # Not a 404 request
            if resolver_match:
                span.set_tag("django.view", resolver_match.view_name)
                utils.set_tag_array(span, "django.namespace",
                                    resolver_match.namespaces)

                # Django >= 2.0.0
                if hasattr(resolver_match, "app_names"):
                    utils.set_tag_array(span, "django.app",
                                        resolver_match.app_names)

            if route:
                span.set_tag("http.route", route)

            # Set HTTP Request tags
            response = func(*args, **kwargs)

            # Note: this call must be done after the function call because
            # some attributes (like `user`) are added to the request through
            # the middleware chain
            _set_request_tags(django, span, request)

            if response:
                status = response.status_code
                span.set_tag("django.response.class", func_name(response))
                if hasattr(response, "template_name"):
                    # template_name is a bit of a misnomer, as it could be any of:
                    # a list of strings, a tuple of strings, a single string, or an instance of Template
                    # for more detail, see:
                    # https://docs.djangoproject.com/en/3.0/ref/template-response/#django.template.response.SimpleTemplateResponse.template_name
                    template = response.template_name

                    if isinstance(template, six.string_types):
                        template_names = [template]
                    elif isinstance(
                            template,
                        (
                            list,
                            tuple,
                        ),
                    ):
                        template_names = template
                    elif hasattr(template, "template"):
                        # ^ checking by attribute here because
                        # django backend implementations don't have a common base
                        # `.template` is also the most consistent across django versions
                        template_names = [template.template.name]
                    else:
                        template_names = None

                    utils.set_tag_array(span, "django.response.template",
                                        template_names)

                url = utils.get_request_uri(request)

                if django.VERSION >= (2, 2, 0):
                    request_headers = request.headers
                else:
                    request_headers = {}
                    for header, value in request.META.items():
                        name = from_wsgi_header(header)
                        if name:
                            request_headers[name] = value

                response_headers = dict(response.items())
                trace_utils.set_http_meta(
                    span,
                    config.django,
                    method=request.method,
                    url=url,
                    status_code=status,
                    query=request.META.get("QUERY_STRING", None),
                    request_headers=request_headers,
                    response_headers=response_headers,
                )

            return response
Exemple #13
0
def _after_request_tags(pin, span, request, response):
    # Response can be None in the event that the request failed
    # We still want to set additional request tags that are resolved
    # during the request.

    try:
        user = getattr(request, "user", None)
        if user is not None:
            # Note: getattr calls to user / user_is_authenticated may result in ImproperlyConfigured exceptions from
            # Django's get_user_model():
            # https://github.com/django/django/blob/a464ead29db8bf6a27a5291cad9eb3f0f3f0472b/django/contrib/auth/__init__.py
            try:
                if hasattr(user, "is_authenticated"):
                    span._set_str_tag("django.user.is_authenticated",
                                      str(user_is_authenticated(user)))

                uid = getattr(user, "pk", None)
                if uid:
                    span._set_str_tag("django.user.id", str(uid))

                if config.django.include_user_name:
                    username = getattr(user, "username", None)
                    if username:
                        span._set_str_tag("django.user.name", username)
            except Exception:
                log.debug(
                    "Error retrieving authentication information for user %r",
                    user,
                    exc_info=True)

        if response:
            status = response.status_code
            span._set_str_tag("django.response.class", func_name(response))
            if hasattr(response, "template_name"):
                # template_name is a bit of a misnomer, as it could be any of:
                # a list of strings, a tuple of strings, a single string, or an instance of Template
                # for more detail, see:
                # https://docs.djangoproject.com/en/3.0/ref/template-response/#django.template.response.SimpleTemplateResponse.template_name
                template = response.template_name

                if isinstance(template, six.string_types):
                    template_names = [template]
                elif isinstance(
                        template,
                    (
                        list,
                        tuple,
                    ),
                ):
                    template_names = template
                elif hasattr(template, "template"):
                    # ^ checking by attribute here because
                    # django backend implementations don't have a common base
                    # `.template` is also the most consistent across django versions
                    template_names = [template.template.name]
                else:
                    template_names = None

                set_tag_array(span, "django.response.template", template_names)

            url = get_request_uri(request)

            if DJANGO22:
                request_headers = request.headers
            else:
                request_headers = {}
                for header, value in request.META.items():
                    name = from_wsgi_header(header)
                    if name:
                        request_headers[name] = value

            # DEV: Resolve the view and resource name at the end of the request in case
            #      urlconf changes at any point during the request
            _set_resolver_tags(pin, span, request)

            response_headers = dict(response.items()) if response else {}
            trace_utils.set_http_meta(
                span,
                config.django,
                method=request.method,
                url=url,
                status_code=status,
                query=request.META.get("QUERY_STRING", None),
                request_headers=request_headers,
                response_headers=response_headers,
            )
    finally:
        if span.resource == REQUEST_DEFAULT_RESOURCE:
            span.resource = request.method
Exemple #14
0
def traced_get_response(django, pin, func, instance, args, kwargs):
    """Trace django.core.handlers.base.BaseHandler.get_response() (or other implementations).

    This is the main entry point for requests.

    Django requests are handled by a Handler.get_response method (inherited from base.BaseHandler).
    This method invokes the middleware chain and returns the response generated by the chain.
    """

    request = kwargs.get("request", args[0])
    if request is None:
        return func(*args, **kwargs)

    try:
        request_headers = request.META

        if config.django.distributed_tracing_enabled:
            context = propagator.extract(request_headers)
            if context.trace_id:
                pin.tracer.context_provider.activate(context)

        # Determine the resolver and resource name for this request
        resolver = get_resolver(getattr(request, "urlconf", None))

        if django.VERSION < (1, 10, 0):
            error_type_404 = django.core.urlresolvers.Resolver404
        else:
            error_type_404 = django.urls.exceptions.Resolver404

        route = None
        resolver_match = None
        resource = request.method
        try:
            # Resolve the requested url
            resolver_match = resolver.resolve(request.path_info)

            # Determine the resource name to use
            # In Django >= 2.2.0 we can access the original route or regex pattern
            if django.VERSION >= (2, 2, 0):
                route = utils.get_django_2_route(resolver, resolver_match)
                if route:
                    resource = "{0} {1}".format(request.method, route)
                else:
                    resource = request.method
            # Older versions just use the view/handler name, e.g. `views.MyView.handler`
            else:
                # TODO: Validate if `resolver.pattern.regex.pattern` is available or not
                callback, callback_args, callback_kwargs = resolver_match
                resource = "{0} {1}".format(request.method,
                                            func_name(callback))

        except error_type_404:
            # Normalize all 404 requests into a single resource name
            # DEV: This is for potential cardinality issues
            resource = "{0} 404".format(request.method)
        except Exception:
            log.debug(
                "Failed to resolve request path %r with path info %r",
                request,
                getattr(request, "path_info", "not-set"),
                exc_info=True,
            )
    except Exception:
        log.debug("Failed to trace django request %r", args, exc_info=True)
        return func(*args, **kwargs)
    else:
        with pin.tracer.trace("django.request",
                              resource=resource,
                              service=config.django["service_name"],
                              span_type=SpanTypes.HTTP) as span:
            analytics_sr = config.django.get_analytics_sample_rate(
                use_global_config=True)
            if analytics_sr is not None:
                span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, analytics_sr)

            if config.django.http.trace_query_string:
                span.set_tag(http.QUERY_STRING,
                             request_headers["QUERY_STRING"])

            # Not a 404 request
            if resolver_match:
                span.set_tag("django.view", resolver_match.view_name)
                utils.set_tag_array(span, "django.namespace",
                                    resolver_match.namespaces)

                # Django >= 2.0.0
                if hasattr(resolver_match, "app_names"):
                    utils.set_tag_array(span, "django.app",
                                        resolver_match.app_names)

            if route:
                span.set_tag("http.route", route)

            # Set HTTP Request tags
            # Build `http.url` tag value from request info
            # DEV: We are explicitly omitting query strings since they may contain sensitive information
            span.set_tag(
                http.URL,
                parse.urlunparse(
                    parse.ParseResult(
                        scheme=request.scheme,
                        netloc=request.get_host(
                        ),  # this will include `host:port`
                        path=request.path,
                        params="",
                        query="",
                        fragment="",
                    )),
            )

            response = func(*args, **kwargs)

            # Note: this call must be done after the function call because
            # some attributes (like `user`) are added to the request through
            # the middleware chain
            _set_request_tags(span, request)

            if response:
                span.set_tag(http.STATUS_CODE, response.status_code)
                if 500 <= response.status_code < 600:
                    span.error = 1
                span.set_tag("django.response.class", func_name(response))
                if hasattr(response, "template_name"):
                    utils.set_tag_array(span, "django.response.template",
                                        response.template_name)
            return response
Exemple #15
0
def traced_get_response(django, pin, func, instance, args, kwargs):
    """Trace django.core.handlers.base.BaseHandler.get_response() (or other implementations).

    This is the main entry point for requests.

    Django requests are handled by a Handler.get_response method (inherited from base.BaseHandler).
    This method invokes the middleware chain and returns the response generated by the chain.
    """

    request = kwargs.get("request", args[0])
    if request is None:
        return func(*args, **kwargs)

    try:
        request_headers = request.META

        if config.django.distributed_tracing_enabled:
            context = propagator.extract(request_headers)
            if context.trace_id:
                pin.tracer.context_provider.activate(context)

        # Determine the resolver and resource name for this request
        resolver = get_resolver(getattr(request, "urlconf", None))

        if django.VERSION < (1, 10, 0):
            error_type_404 = django.core.urlresolvers.Resolver404
        else:
            error_type_404 = django.urls.exceptions.Resolver404

        route = None
        resolver_match = None
        resource = request.method
        try:
            # Resolve the requested url
            resolver_match = resolver.resolve(request.path_info)

            # Determine the resource name to use
            # In Django >= 2.2.0 we can access the original route or regex pattern
            if django.VERSION >= (2, 2, 0):
                route = utils.get_django_2_route(resolver, resolver_match)
                if route:
                    resource = "{0} {1}".format(request.method, route)
                else:
                    resource = request.method
            # Older versions just use the view/handler name, e.g. `views.MyView.handler`
            else:
                # TODO: Validate if `resolver.pattern.regex.pattern` is available or not
                callback, callback_args, callback_kwargs = resolver_match
                resource = "{0} {1}".format(request.method,
                                            func_name(callback))

        except error_type_404:
            # Normalize all 404 requests into a single resource name
            # DEV: This is for potential cardinality issues
            resource = "{0} 404".format(request.method)
        except Exception:
            log.debug(
                "Failed to resolve request path %r with path info %r",
                request,
                getattr(request, "path_info", "not-set"),
                exc_info=True,
            )
    except Exception:
        log.debug("Failed to trace django request %r", args, exc_info=True)
        return func(*args, **kwargs)
    else:
        with pin.tracer.trace("django.request",
                              resource=resource,
                              service=config.django["service_name"],
                              span_type=SpanTypes.HTTP) as span:
            analytics_sr = config.django.get_analytics_sample_rate(
                use_global_config=True)
            if analytics_sr is not None:
                span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, analytics_sr)

            if config.django.http.trace_query_string:
                span.set_tag(http.QUERY_STRING,
                             request_headers["QUERY_STRING"])

            # Not a 404 request
            if resolver_match:
                span.set_tag("django.view", resolver_match.view_name)
                utils.set_tag_array(span, "django.namespace",
                                    resolver_match.namespaces)

                # Django >= 2.0.0
                if hasattr(resolver_match, "app_names"):
                    utils.set_tag_array(span, "django.app",
                                        resolver_match.app_names)

            if route:
                span.set_tag("http.route", route)

            # Set HTTP Request tags
            span.set_tag(http.URL, utils.get_request_uri(request))

            response = func(*args, **kwargs)

            # Note: this call must be done after the function call because
            # some attributes (like `user`) are added to the request through
            # the middleware chain
            _set_request_tags(span, request)

            if response:
                span.set_tag(http.STATUS_CODE, response.status_code)
                if 500 <= response.status_code < 600:
                    span.error = 1
                span.set_tag("django.response.class", func_name(response))
                if hasattr(response, "template_name"):
                    # template_name is a bit of a misnomer, as it could be any of:
                    # a list of strings, a tuple of strings, a single string, or an instance of Template
                    # for more detail, see:
                    # https://docs.djangoproject.com/en/3.0/ref/template-response/#django.template.response.SimpleTemplateResponse.template_name
                    template = response.template_name

                    if isinstance(template, six.string_types):
                        template_names = [template]
                    elif isinstance(template, (
                            list,
                            tuple,
                    )):
                        template_names = template
                    elif hasattr(template, "template"):
                        # ^ checking by attribute here because
                        # django backend implementations don't have a common base
                        # `.template` is also the most consistent across django versions
                        template_names = [template.template.name]
                    else:
                        template_names = None

                    utils.set_tag_array(span, "django.response.template",
                                        template_names)

            return response