We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Instrument the Django REST Framework (DRF)
Provide more detailed instrumentation of the Django REST Framework (DRF).

http://www.django-rest-framework.org/

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/scout_apm/django/middleware.py`
Content:
```
1 # coding=utf-8
2 from __future__ import absolute_import, division, print_function, unicode_literals
3 
4 import sys
5 
6 import django
7 from django.conf import settings
8 
9 from scout_apm.compat import string_types
10 from scout_apm.core.config import scout_config
11 from scout_apm.core.tracked_request import TrackedRequest
12 from scout_apm.core.web_requests import (
13     create_filtered_path,
14     ignore_path,
15     track_amazon_request_queue_time,
16     track_request_queue_time,
17 )
18 
19 if django.VERSION >= (1, 11):
20     from django.urls import get_urlconf
21 else:
22     from django.core.urlresolvers import get_urlconf
23 
24 
25 def get_operation_name(request):
26     view_func = request.resolver_match.func
27     view_name = request.resolver_match._func_path
28 
29     if hasattr(view_func, "model_admin"):
30         # Seems to comes from Django admin (attribute only set on Django 1.9+)
31         admin_class = view_func.model_admin.__class__
32         view_name = (
33             admin_class.__module__
34             + "."
35             + admin_class.__name__
36             + "."
37             + view_func.__name__
38         )
39 
40     # Seems to be a Tastypie Resource. Need to resort to some stack inspection
41     # to find a better name since its decorators don't wrap very well
42     if view_name == "tastypie.resources.wrapper":
43         tastypie_name = _get_tastypie_operation_name(request, view_func)
44         if tastypie_name is not None:
45             return tastypie_name
46 
47     return "Controller/" + view_name
48 
49 
50 def _get_tastypie_operation_name(request, view_func):
51     try:
52         from tastypie.resources import Resource
53     except ImportError:
54         return None
55 
56     if sys.version_info[0] == 2:  # pragma: no cover
57         try:
58             wrapper = view_func.__closure__[0].cell_contents
59         except (AttributeError, IndexError):
60             return None
61     elif sys.version_info[0] == 3:
62         try:
63             wrapper = view_func.__wrapped__
64         except AttributeError:
65             return None
66 
67     if not hasattr(wrapper, "__closure__") or len(wrapper.__closure__) != 2:
68         return None
69 
70     instance = wrapper.__closure__[0].cell_contents
71     if not isinstance(instance, Resource):  # pragma: no cover
72         return None
73 
74     method_name = wrapper.__closure__[1].cell_contents
75     if not isinstance(method_name, string_types):  # pragma: no cover
76         return None
77 
78     if method_name.startswith("dispatch_"):  # pragma: no cover
79         method_name = request.method.lower() + method_name.split("dispatch", 1)[1]
80 
81     return "Controller/{}.{}.{}".format(
82         instance.__module__, instance.__class__.__name__, method_name
83     )
84 
85 
86 def track_request_view_data(request, tracked_request):
87     path = request.path
88     tracked_request.tag(
89         "path",
90         create_filtered_path(
91             path, [(k, v) for k, vs in request.GET.lists() for v in vs]
92         ),
93     )
94     if ignore_path(path):
95         tracked_request.tag("ignore_transaction", True)
96 
97     try:
98         # Determine a remote IP to associate with the request. The value is
99         # spoofable by the requester so this is not suitable to use in any
100         # security sensitive context.
101         user_ip = (
102             request.META.get("HTTP_X_FORWARDED_FOR", "").split(",")[0]
103             or request.META.get("HTTP_CLIENT_IP", "").split(",")[0]
104             or request.META.get("REMOTE_ADDR", None)
105         )
106         tracked_request.tag("user_ip", user_ip)
107     except Exception:
108         pass
109 
110     user = getattr(request, "user", None)
111     if user is not None:
112         try:
113             tracked_request.tag("username", user.get_username())
114         except Exception:
115             pass
116 
117     tracked_request.tag("urlconf", get_urlconf(settings.ROOT_URLCONF))
118 
119 
120 class MiddlewareTimingMiddleware(object):
121     """
122     Insert as early into the Middleware stack as possible (outermost layers),
123     so that other middlewares called after can be timed.
124     """
125 
126     def __init__(self, get_response):
127         self.get_response = get_response
128 
129     def __call__(self, request):
130         if not scout_config.value("monitor"):
131             return self.get_response(request)
132 
133         tracked_request = TrackedRequest.instance()
134 
135         tracked_request.start_span(
136             operation="Middleware", should_capture_backtrace=False
137         )
138         queue_time = request.META.get("HTTP_X_QUEUE_START") or request.META.get(
139             "HTTP_X_REQUEST_START", ""
140         )
141         queue_time_tracked = track_request_queue_time(queue_time, tracked_request)
142         if not queue_time_tracked:
143             track_amazon_request_queue_time(
144                 request.META.get("HTTP_X_AMZN_TRACE_ID", ""), tracked_request
145             )
146 
147         try:
148             return self.get_response(request)
149         finally:
150             tracked_request.stop_span()
151 
152 
153 class ViewTimingMiddleware(object):
154     """
155     Insert as deep into the middleware stack as possible, ideally wrapping no
156     other middleware. Designed to time the View itself
157     """
158 
159     def __init__(self, get_response):
160         self.get_response = get_response
161 
162     def __call__(self, request):
163         """
164         Wrap a single incoming request with start and stop calls.
165         This will start timing, but relies on the process_view callback to
166         capture more details about what view was really called, and other
167         similar info.
168 
169         If process_view isn't called, then the request will not
170         be recorded.  This can happen if a middleware further along the stack
171         doesn't call onward, and instead returns a response directly.
172         """
173         if not scout_config.value("monitor"):
174             return self.get_response(request)
175 
176         tracked_request = TrackedRequest.instance()
177 
178         # This operation name won't be recorded unless changed later in
179         # process_view
180         tracked_request.start_span(operation="Unknown", should_capture_backtrace=False)
181         try:
182             response = self.get_response(request)
183             if 500 <= response.status_code <= 599:
184                 tracked_request.tag("error", "true")
185             return response
186         finally:
187             tracked_request.stop_span()
188 
189     def process_view(self, request, view_func, view_args, view_kwargs):
190         """
191         Capture details about the view_func that is about to execute
192         """
193         if not scout_config.value("monitor"):
194             return
195         tracked_request = TrackedRequest.instance()
196         tracked_request.is_real_request = True
197 
198         track_request_view_data(request, tracked_request)
199 
200         span = tracked_request.current_span()
201         if span is not None:
202             span.operation = get_operation_name(request)
203 
204     def process_exception(self, request, exception):
205         """
206         Mark this request as having errored out
207 
208         Does not modify or catch or otherwise change the exception thrown
209         """
210         if not scout_config.value("monitor"):
211             return
212         TrackedRequest.instance().tag("error", "true")
213 
214 
215 class OldStyleMiddlewareTimingMiddleware(object):
216     """
217     Insert as early into the Middleware stack as possible (outermost layers),
218     so that other middlewares called after can be timed.
219     """
220 
221     def process_request(self, request):
222         if not scout_config.value("monitor"):
223             return
224         tracked_request = TrackedRequest.instance()
225         request._scout_tracked_request = tracked_request
226 
227         queue_time = request.META.get("HTTP_X_QUEUE_START") or request.META.get(
228             "HTTP_X_REQUEST_START", ""
229         )
230         queue_time_tracked = track_request_queue_time(queue_time, tracked_request)
231         if not queue_time_tracked:
232             track_amazon_request_queue_time(
233                 request.META.get("HTTP_X_AMZN_TRACE_ID", ""), tracked_request
234             )
235 
236         tracked_request.start_span(
237             operation="Middleware", should_capture_backtrace=False
238         )
239 
240     def process_response(self, request, response):
241         # Only stop span if there's a request, but presume we are balanced,
242         # i.e. that custom instrumentation within the application is not
243         # causing errors
244         tracked_request = getattr(request, "_scout_tracked_request", None)
245         if 500 <= response.status_code <= 599:
246             tracked_request.tag("error", "true")
247         if tracked_request is not None:
248             tracked_request.stop_span()
249         return response
250 
251 
252 class OldStyleViewMiddleware(object):
253     def process_view(self, request, view_func, view_func_args, view_func_kwargs):
254         tracked_request = getattr(request, "_scout_tracked_request", None)
255         if tracked_request is None:
256             # Looks like OldStyleMiddlewareTimingMiddleware didn't run, so
257             # don't do anything
258             return
259 
260         tracked_request.is_real_request = True
261 
262         track_request_view_data(request, tracked_request)
263 
264         span = tracked_request.start_span(
265             operation=get_operation_name(request), should_capture_backtrace=False
266         )
267         # Save the span into the request, so we can check
268         # if we're matched up when stopping
269         request._scout_view_span = span
270 
271     def process_response(self, request, response):
272         tracked_request = getattr(request, "_scout_tracked_request", None)
273         if tracked_request is None:
274             # Looks like OldStyleMiddlewareTimingMiddleware didn't run, so
275             # don't do anything
276             return response
277 
278         # Only stop span if we started, but presume we are balanced, i.e. that
279         # custom instrumentation within the application is not causing errors
280         span = getattr(request, "_scout_view_span", None)
281         if span is not None:
282             tracked_request.stop_span()
283         return response
284 
285     def process_exception(self, request, exception):
286         tracked_request = getattr(request, "_scout_tracked_request", None)
287         if tracked_request is None:
288             # Looks like OldStyleMiddlewareTimingMiddleware didn't run, so
289             # don't do anything
290             return
291 
292         tracked_request.tag("error", "true")
293
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/scout_apm/django/middleware.py b/src/scout_apm/django/middleware.py
--- a/src/scout_apm/django/middleware.py
+++ b/src/scout_apm/django/middleware.py
@@ -37,6 +37,12 @@
             + view_func.__name__
         )
 
+    django_rest_framework_name = _get_django_rest_framework_name(
+        request, view_func, view_name
+    )
+    if django_rest_framework_name is not None:
+        return django_rest_framework_name
+
     # Seems to be a Tastypie Resource. Need to resort to some stack inspection
     # to find a better name since its decorators don't wrap very well
     if view_name == "tastypie.resources.wrapper":
@@ -47,6 +53,28 @@
     return "Controller/" + view_name
 
 
+def _get_django_rest_framework_name(request, view_func, view_name):
+    try:
+        from rest_framework.viewsets import ViewSetMixin
+    except ImportError:
+        return None
+
+    kls = getattr(view_func, "cls", None)
+    if isinstance(kls, type) and not issubclass(kls, ViewSetMixin):
+        return None
+
+    # Get 'actions' set in ViewSetMixin.as_view
+    actions = getattr(view_func, "actions", None)
+    if not actions or not isinstance(actions, dict):
+        return None
+
+    method_lower = request.method.lower()
+    if method_lower not in actions:
+        return None
+
+    return "Controller/{}.{}".format(view_name, actions[method_lower])
+
+
 def _get_tastypie_operation_name(request, view_func):
     try:
         from tastypie.resources import Resource

{"golden_diff": "diff --git a/src/scout_apm/django/middleware.py b/src/scout_apm/django/middleware.py\n--- a/src/scout_apm/django/middleware.py\n+++ b/src/scout_apm/django/middleware.py\n@@ -37,6 +37,12 @@\n             + view_func.__name__\n         )\n \n+    django_rest_framework_name = _get_django_rest_framework_name(\n+        request, view_func, view_name\n+    )\n+    if django_rest_framework_name is not None:\n+        return django_rest_framework_name\n+\n     # Seems to be a Tastypie Resource. Need to resort to some stack inspection\n     # to find a better name since its decorators don't wrap very well\n     if view_name == \"tastypie.resources.wrapper\":\n@@ -47,6 +53,28 @@\n     return \"Controller/\" + view_name\n \n \n+def _get_django_rest_framework_name(request, view_func, view_name):\n+    try:\n+        from rest_framework.viewsets import ViewSetMixin\n+    except ImportError:\n+        return None\n+\n+    kls = getattr(view_func, \"cls\", None)\n+    if isinstance(kls, type) and not issubclass(kls, ViewSetMixin):\n+        return None\n+\n+    # Get 'actions' set in ViewSetMixin.as_view\n+    actions = getattr(view_func, \"actions\", None)\n+    if not actions or not isinstance(actions, dict):\n+        return None\n+\n+    method_lower = request.method.lower()\n+    if method_lower not in actions:\n+        return None\n+\n+    return \"Controller/{}.{}\".format(view_name, actions[method_lower])\n+\n+\n def _get_tastypie_operation_name(request, view_func):\n     try:\n         from tastypie.resources import Resource\n", "issue": "Instrument the Django REST Framework (DRF)\nProvide more detailed instrumentation of the Django REST Framework (DRF).\r\n\r\nhttp://www.django-rest-framework.org/\n", "before_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport sys\n\nimport django\nfrom django.conf import settings\n\nfrom scout_apm.compat import string_types\nfrom scout_apm.core.config import scout_config\nfrom scout_apm.core.tracked_request import TrackedRequest\nfrom scout_apm.core.web_requests import (\n    create_filtered_path,\n    ignore_path,\n    track_amazon_request_queue_time,\n    track_request_queue_time,\n)\n\nif django.VERSION >= (1, 11):\n    from django.urls import get_urlconf\nelse:\n    from django.core.urlresolvers import get_urlconf\n\n\ndef get_operation_name(request):\n    view_func = request.resolver_match.func\n    view_name = request.resolver_match._func_path\n\n    if hasattr(view_func, \"model_admin\"):\n        # Seems to comes from Django admin (attribute only set on Django 1.9+)\n        admin_class = view_func.model_admin.__class__\n        view_name = (\n            admin_class.__module__\n            + \".\"\n            + admin_class.__name__\n            + \".\"\n            + view_func.__name__\n        )\n\n    # Seems to be a Tastypie Resource. Need to resort to some stack inspection\n    # to find a better name since its decorators don't wrap very well\n    if view_name == \"tastypie.resources.wrapper\":\n        tastypie_name = _get_tastypie_operation_name(request, view_func)\n        if tastypie_name is not None:\n            return tastypie_name\n\n    return \"Controller/\" + view_name\n\n\ndef _get_tastypie_operation_name(request, view_func):\n    try:\n        from tastypie.resources import Resource\n    except ImportError:\n        return None\n\n    if sys.version_info[0] == 2:  # pragma: no cover\n        try:\n            wrapper = view_func.__closure__[0].cell_contents\n        except (AttributeError, IndexError):\n            return None\n    elif sys.version_info[0] == 3:\n        try:\n            wrapper = view_func.__wrapped__\n        except AttributeError:\n            return None\n\n    if not hasattr(wrapper, \"__closure__\") or len(wrapper.__closure__) != 2:\n        return None\n\n    instance = wrapper.__closure__[0].cell_contents\n    if not isinstance(instance, Resource):  # pragma: no cover\n        return None\n\n    method_name = wrapper.__closure__[1].cell_contents\n    if not isinstance(method_name, string_types):  # pragma: no cover\n        return None\n\n    if method_name.startswith(\"dispatch_\"):  # pragma: no cover\n        method_name = request.method.lower() + method_name.split(\"dispatch\", 1)[1]\n\n    return \"Controller/{}.{}.{}\".format(\n        instance.__module__, instance.__class__.__name__, method_name\n    )\n\n\ndef track_request_view_data(request, tracked_request):\n    path = request.path\n    tracked_request.tag(\n        \"path\",\n        create_filtered_path(\n            path, [(k, v) for k, vs in request.GET.lists() for v in vs]\n        ),\n    )\n    if ignore_path(path):\n        tracked_request.tag(\"ignore_transaction\", True)\n\n    try:\n        # Determine a remote IP to associate with the request. The value is\n        # spoofable by the requester so this is not suitable to use in any\n        # security sensitive context.\n        user_ip = (\n            request.META.get(\"HTTP_X_FORWARDED_FOR\", \"\").split(\",\")[0]\n            or request.META.get(\"HTTP_CLIENT_IP\", \"\").split(\",\")[0]\n            or request.META.get(\"REMOTE_ADDR\", None)\n        )\n        tracked_request.tag(\"user_ip\", user_ip)\n    except Exception:\n        pass\n\n    user = getattr(request, \"user\", None)\n    if user is not None:\n        try:\n            tracked_request.tag(\"username\", user.get_username())\n        except Exception:\n            pass\n\n    tracked_request.tag(\"urlconf\", get_urlconf(settings.ROOT_URLCONF))\n\n\nclass MiddlewareTimingMiddleware(object):\n    \"\"\"\n    Insert as early into the Middleware stack as possible (outermost layers),\n    so that other middlewares called after can be timed.\n    \"\"\"\n\n    def __init__(self, get_response):\n        self.get_response = get_response\n\n    def __call__(self, request):\n        if not scout_config.value(\"monitor\"):\n            return self.get_response(request)\n\n        tracked_request = TrackedRequest.instance()\n\n        tracked_request.start_span(\n            operation=\"Middleware\", should_capture_backtrace=False\n        )\n        queue_time = request.META.get(\"HTTP_X_QUEUE_START\") or request.META.get(\n            \"HTTP_X_REQUEST_START\", \"\"\n        )\n        queue_time_tracked = track_request_queue_time(queue_time, tracked_request)\n        if not queue_time_tracked:\n            track_amazon_request_queue_time(\n                request.META.get(\"HTTP_X_AMZN_TRACE_ID\", \"\"), tracked_request\n            )\n\n        try:\n            return self.get_response(request)\n        finally:\n            tracked_request.stop_span()\n\n\nclass ViewTimingMiddleware(object):\n    \"\"\"\n    Insert as deep into the middleware stack as possible, ideally wrapping no\n    other middleware. Designed to time the View itself\n    \"\"\"\n\n    def __init__(self, get_response):\n        self.get_response = get_response\n\n    def __call__(self, request):\n        \"\"\"\n        Wrap a single incoming request with start and stop calls.\n        This will start timing, but relies on the process_view callback to\n        capture more details about what view was really called, and other\n        similar info.\n\n        If process_view isn't called, then the request will not\n        be recorded.  This can happen if a middleware further along the stack\n        doesn't call onward, and instead returns a response directly.\n        \"\"\"\n        if not scout_config.value(\"monitor\"):\n            return self.get_response(request)\n\n        tracked_request = TrackedRequest.instance()\n\n        # This operation name won't be recorded unless changed later in\n        # process_view\n        tracked_request.start_span(operation=\"Unknown\", should_capture_backtrace=False)\n        try:\n            response = self.get_response(request)\n            if 500 <= response.status_code <= 599:\n                tracked_request.tag(\"error\", \"true\")\n            return response\n        finally:\n            tracked_request.stop_span()\n\n    def process_view(self, request, view_func, view_args, view_kwargs):\n        \"\"\"\n        Capture details about the view_func that is about to execute\n        \"\"\"\n        if not scout_config.value(\"monitor\"):\n            return\n        tracked_request = TrackedRequest.instance()\n        tracked_request.is_real_request = True\n\n        track_request_view_data(request, tracked_request)\n\n        span = tracked_request.current_span()\n        if span is not None:\n            span.operation = get_operation_name(request)\n\n    def process_exception(self, request, exception):\n        \"\"\"\n        Mark this request as having errored out\n\n        Does not modify or catch or otherwise change the exception thrown\n        \"\"\"\n        if not scout_config.value(\"monitor\"):\n            return\n        TrackedRequest.instance().tag(\"error\", \"true\")\n\n\nclass OldStyleMiddlewareTimingMiddleware(object):\n    \"\"\"\n    Insert as early into the Middleware stack as possible (outermost layers),\n    so that other middlewares called after can be timed.\n    \"\"\"\n\n    def process_request(self, request):\n        if not scout_config.value(\"monitor\"):\n            return\n        tracked_request = TrackedRequest.instance()\n        request._scout_tracked_request = tracked_request\n\n        queue_time = request.META.get(\"HTTP_X_QUEUE_START\") or request.META.get(\n            \"HTTP_X_REQUEST_START\", \"\"\n        )\n        queue_time_tracked = track_request_queue_time(queue_time, tracked_request)\n        if not queue_time_tracked:\n            track_amazon_request_queue_time(\n                request.META.get(\"HTTP_X_AMZN_TRACE_ID\", \"\"), tracked_request\n            )\n\n        tracked_request.start_span(\n            operation=\"Middleware\", should_capture_backtrace=False\n        )\n\n    def process_response(self, request, response):\n        # Only stop span if there's a request, but presume we are balanced,\n        # i.e. that custom instrumentation within the application is not\n        # causing errors\n        tracked_request = getattr(request, \"_scout_tracked_request\", None)\n        if 500 <= response.status_code <= 599:\n            tracked_request.tag(\"error\", \"true\")\n        if tracked_request is not None:\n            tracked_request.stop_span()\n        return response\n\n\nclass OldStyleViewMiddleware(object):\n    def process_view(self, request, view_func, view_func_args, view_func_kwargs):\n        tracked_request = getattr(request, \"_scout_tracked_request\", None)\n        if tracked_request is None:\n            # Looks like OldStyleMiddlewareTimingMiddleware didn't run, so\n            # don't do anything\n            return\n\n        tracked_request.is_real_request = True\n\n        track_request_view_data(request, tracked_request)\n\n        span = tracked_request.start_span(\n            operation=get_operation_name(request), should_capture_backtrace=False\n        )\n        # Save the span into the request, so we can check\n        # if we're matched up when stopping\n        request._scout_view_span = span\n\n    def process_response(self, request, response):\n        tracked_request = getattr(request, \"_scout_tracked_request\", None)\n        if tracked_request is None:\n            # Looks like OldStyleMiddlewareTimingMiddleware didn't run, so\n            # don't do anything\n            return response\n\n        # Only stop span if we started, but presume we are balanced, i.e. that\n        # custom instrumentation within the application is not causing errors\n        span = getattr(request, \"_scout_view_span\", None)\n        if span is not None:\n            tracked_request.stop_span()\n        return response\n\n    def process_exception(self, request, exception):\n        tracked_request = getattr(request, \"_scout_tracked_request\", None)\n        if tracked_request is None:\n            # Looks like OldStyleMiddlewareTimingMiddleware didn't run, so\n            # don't do anything\n            return\n\n        tracked_request.tag(\"error\", \"true\")\n", "path": "src/scout_apm/django/middleware.py"}], "after_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport sys\n\nimport django\nfrom django.conf import settings\n\nfrom scout_apm.compat import string_types\nfrom scout_apm.core.config import scout_config\nfrom scout_apm.core.tracked_request import TrackedRequest\nfrom scout_apm.core.web_requests import (\n    create_filtered_path,\n    ignore_path,\n    track_amazon_request_queue_time,\n    track_request_queue_time,\n)\n\nif django.VERSION >= (1, 11):\n    from django.urls import get_urlconf\nelse:\n    from django.core.urlresolvers import get_urlconf\n\n\ndef get_operation_name(request):\n    view_func = request.resolver_match.func\n    view_name = request.resolver_match._func_path\n\n    if hasattr(view_func, \"model_admin\"):\n        # Seems to comes from Django admin (attribute only set on Django 1.9+)\n        admin_class = view_func.model_admin.__class__\n        view_name = (\n            admin_class.__module__\n            + \".\"\n            + admin_class.__name__\n            + \".\"\n            + view_func.__name__\n        )\n\n    django_rest_framework_name = _get_django_rest_framework_name(\n        request, view_func, view_name\n    )\n    if django_rest_framework_name is not None:\n        return django_rest_framework_name\n\n    # Seems to be a Tastypie Resource. Need to resort to some stack inspection\n    # to find a better name since its decorators don't wrap very well\n    if view_name == \"tastypie.resources.wrapper\":\n        tastypie_name = _get_tastypie_operation_name(request, view_func)\n        if tastypie_name is not None:\n            return tastypie_name\n\n    return \"Controller/\" + view_name\n\n\ndef _get_django_rest_framework_name(request, view_func, view_name):\n    try:\n        from rest_framework.viewsets import ViewSetMixin\n    except ImportError:\n        return None\n\n    kls = getattr(view_func, \"cls\", None)\n    if isinstance(kls, type) and not issubclass(kls, ViewSetMixin):\n        return None\n\n    # Get 'actions' set in ViewSetMixin.as_view\n    actions = getattr(view_func, \"actions\", None)\n    if not actions or not isinstance(actions, dict):\n        return None\n\n    method_lower = request.method.lower()\n    if method_lower not in actions:\n        return None\n\n    return \"Controller/{}.{}\".format(view_name, actions[method_lower])\n\n\ndef _get_tastypie_operation_name(request, view_func):\n    try:\n        from tastypie.resources import Resource\n    except ImportError:\n        return None\n\n    if sys.version_info[0] == 2:  # pragma: no cover\n        try:\n            wrapper = view_func.__closure__[0].cell_contents\n        except (AttributeError, IndexError):\n            return None\n    elif sys.version_info[0] == 3:\n        try:\n            wrapper = view_func.__wrapped__\n        except AttributeError:\n            return None\n\n    if not hasattr(wrapper, \"__closure__\") or len(wrapper.__closure__) != 2:\n        return None\n\n    instance = wrapper.__closure__[0].cell_contents\n    if not isinstance(instance, Resource):  # pragma: no cover\n        return None\n\n    method_name = wrapper.__closure__[1].cell_contents\n    if not isinstance(method_name, string_types):  # pragma: no cover\n        return None\n\n    if method_name.startswith(\"dispatch_\"):  # pragma: no cover\n        method_name = request.method.lower() + method_name.split(\"dispatch\", 1)[1]\n\n    return \"Controller/{}.{}.{}\".format(\n        instance.__module__, instance.__class__.__name__, method_name\n    )\n\n\ndef track_request_view_data(request, tracked_request):\n    path = request.path\n    tracked_request.tag(\n        \"path\",\n        create_filtered_path(\n            path, [(k, v) for k, vs in request.GET.lists() for v in vs]\n        ),\n    )\n    if ignore_path(path):\n        tracked_request.tag(\"ignore_transaction\", True)\n\n    try:\n        # Determine a remote IP to associate with the request. The value is\n        # spoofable by the requester so this is not suitable to use in any\n        # security sensitive context.\n        user_ip = (\n            request.META.get(\"HTTP_X_FORWARDED_FOR\", \"\").split(\",\")[0]\n            or request.META.get(\"HTTP_CLIENT_IP\", \"\").split(\",\")[0]\n            or request.META.get(\"REMOTE_ADDR\", None)\n        )\n        tracked_request.tag(\"user_ip\", user_ip)\n    except Exception:\n        pass\n\n    user = getattr(request, \"user\", None)\n    if user is not None:\n        try:\n            tracked_request.tag(\"username\", user.get_username())\n        except Exception:\n            pass\n\n    tracked_request.tag(\"urlconf\", get_urlconf(settings.ROOT_URLCONF))\n\n\nclass MiddlewareTimingMiddleware(object):\n    \"\"\"\n    Insert as early into the Middleware stack as possible (outermost layers),\n    so that other middlewares called after can be timed.\n    \"\"\"\n\n    def __init__(self, get_response):\n        self.get_response = get_response\n\n    def __call__(self, request):\n        if not scout_config.value(\"monitor\"):\n            return self.get_response(request)\n\n        tracked_request = TrackedRequest.instance()\n\n        tracked_request.start_span(\n            operation=\"Middleware\", should_capture_backtrace=False\n        )\n        queue_time = request.META.get(\"HTTP_X_QUEUE_START\") or request.META.get(\n            \"HTTP_X_REQUEST_START\", \"\"\n        )\n        queue_time_tracked = track_request_queue_time(queue_time, tracked_request)\n        if not queue_time_tracked:\n            track_amazon_request_queue_time(\n                request.META.get(\"HTTP_X_AMZN_TRACE_ID\", \"\"), tracked_request\n            )\n\n        try:\n            return self.get_response(request)\n        finally:\n            tracked_request.stop_span()\n\n\nclass ViewTimingMiddleware(object):\n    \"\"\"\n    Insert as deep into the middleware stack as possible, ideally wrapping no\n    other middleware. Designed to time the View itself\n    \"\"\"\n\n    def __init__(self, get_response):\n        self.get_response = get_response\n\n    def __call__(self, request):\n        \"\"\"\n        Wrap a single incoming request with start and stop calls.\n        This will start timing, but relies on the process_view callback to\n        capture more details about what view was really called, and other\n        similar info.\n\n        If process_view isn't called, then the request will not\n        be recorded.  This can happen if a middleware further along the stack\n        doesn't call onward, and instead returns a response directly.\n        \"\"\"\n        if not scout_config.value(\"monitor\"):\n            return self.get_response(request)\n\n        tracked_request = TrackedRequest.instance()\n\n        # This operation name won't be recorded unless changed later in\n        # process_view\n        tracked_request.start_span(operation=\"Unknown\", should_capture_backtrace=False)\n        try:\n            response = self.get_response(request)\n            if 500 <= response.status_code <= 599:\n                tracked_request.tag(\"error\", \"true\")\n            return response\n        finally:\n            tracked_request.stop_span()\n\n    def process_view(self, request, view_func, view_args, view_kwargs):\n        \"\"\"\n        Capture details about the view_func that is about to execute\n        \"\"\"\n        if not scout_config.value(\"monitor\"):\n            return\n        tracked_request = TrackedRequest.instance()\n        tracked_request.is_real_request = True\n\n        track_request_view_data(request, tracked_request)\n\n        span = tracked_request.current_span()\n        if span is not None:\n            span.operation = get_operation_name(request)\n\n    def process_exception(self, request, exception):\n        \"\"\"\n        Mark this request as having errored out\n\n        Does not modify or catch or otherwise change the exception thrown\n        \"\"\"\n        if not scout_config.value(\"monitor\"):\n            return\n        TrackedRequest.instance().tag(\"error\", \"true\")\n\n\nclass OldStyleMiddlewareTimingMiddleware(object):\n    \"\"\"\n    Insert as early into the Middleware stack as possible (outermost layers),\n    so that other middlewares called after can be timed.\n    \"\"\"\n\n    def process_request(self, request):\n        if not scout_config.value(\"monitor\"):\n            return\n        tracked_request = TrackedRequest.instance()\n        request._scout_tracked_request = tracked_request\n\n        queue_time = request.META.get(\"HTTP_X_QUEUE_START\") or request.META.get(\n            \"HTTP_X_REQUEST_START\", \"\"\n        )\n        queue_time_tracked = track_request_queue_time(queue_time, tracked_request)\n        if not queue_time_tracked:\n            track_amazon_request_queue_time(\n                request.META.get(\"HTTP_X_AMZN_TRACE_ID\", \"\"), tracked_request\n            )\n\n        tracked_request.start_span(\n            operation=\"Middleware\", should_capture_backtrace=False\n        )\n\n    def process_response(self, request, response):\n        # Only stop span if there's a request, but presume we are balanced,\n        # i.e. that custom instrumentation within the application is not\n        # causing errors\n        tracked_request = getattr(request, \"_scout_tracked_request\", None)\n        if 500 <= response.status_code <= 599:\n            tracked_request.tag(\"error\", \"true\")\n        if tracked_request is not None:\n            tracked_request.stop_span()\n        return response\n\n\nclass OldStyleViewMiddleware(object):\n    def process_view(self, request, view_func, view_func_args, view_func_kwargs):\n        tracked_request = getattr(request, \"_scout_tracked_request\", None)\n        if tracked_request is None:\n            # Looks like OldStyleMiddlewareTimingMiddleware didn't run, so\n            # don't do anything\n            return\n\n        tracked_request.is_real_request = True\n\n        track_request_view_data(request, tracked_request)\n\n        span = tracked_request.start_span(\n            operation=get_operation_name(request), should_capture_backtrace=False\n        )\n        # Save the span into the request, so we can check\n        # if we're matched up when stopping\n        request._scout_view_span = span\n\n    def process_response(self, request, response):\n        tracked_request = getattr(request, \"_scout_tracked_request\", None)\n        if tracked_request is None:\n            # Looks like OldStyleMiddlewareTimingMiddleware didn't run, so\n            # don't do anything\n            return response\n\n        # Only stop span if we started, but presume we are balanced, i.e. that\n        # custom instrumentation within the application is not causing errors\n        span = getattr(request, \"_scout_view_span\", None)\n        if span is not None:\n            tracked_request.stop_span()\n        return response\n\n    def process_exception(self, request, exception):\n        tracked_request = getattr(request, \"_scout_tracked_request\", None)\n        if tracked_request is None:\n            # Looks like OldStyleMiddlewareTimingMiddleware didn't run, so\n            # don't do anything\n            return\n\n        tracked_request.tag(\"error\", \"true\")\n", "path": "src/scout_apm/django/middleware.py"}]}