We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
scout breaks caching for django
We gave scout a try and looked promising but then we quickly had to disable it on production.

Issue is that scout is adding a `Vary: Cookie` header which breaks caching



This normally happens when some code in django accesses the request.user object. I'm assuming scout is trying to add some meta data and accesses it causing this issue.

We've run into this problem ourselves in the past and the way around is it to look for the internal cached user on the request object. Like this:

```
    # going request.user will generate cookie vary headers, but since
    # we aren't changing the output based on this we want to see the user
    # without adding the header, so look for the lazy user
    if request and hasattr(request, '_cached_user'):
        # noinspection PyProtectedMember
        user = request._cached_user
```

I think if replaced the check for user here

https://github.com/scoutapp/scout_apm_python/blob/master/src/scout_apm/django/middleware.py#L139




--- 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     django_rest_framework_name = _get_django_rest_framework_name(
41         request, view_func, view_name
42     )
43     if django_rest_framework_name is not None:
44         return django_rest_framework_name
45 
46     # Seems to be a Tastypie Resource. Need to resort to some stack inspection
47     # to find a better name since its decorators don't wrap very well
48     if view_name == "tastypie.resources.wrapper":
49         tastypie_name = _get_tastypie_operation_name(request, view_func)
50         if tastypie_name is not None:
51             return tastypie_name
52 
53     return "Controller/" + view_name
54 
55 
56 def _get_django_rest_framework_name(request, view_func, view_name):
57     try:
58         from rest_framework.viewsets import ViewSetMixin
59     except ImportError:
60         return None
61 
62     kls = getattr(view_func, "cls", None)
63     if isinstance(kls, type) and not issubclass(kls, ViewSetMixin):
64         return None
65 
66     # Get 'actions' set in ViewSetMixin.as_view
67     actions = getattr(view_func, "actions", None)
68     if not actions or not isinstance(actions, dict):
69         return None
70 
71     method_lower = request.method.lower()
72     if method_lower not in actions:
73         return None
74 
75     return "Controller/{}.{}".format(view_name, actions[method_lower])
76 
77 
78 def _get_tastypie_operation_name(request, view_func):
79     try:
80         from tastypie.resources import Resource
81     except ImportError:
82         return None
83 
84     if sys.version_info[0] == 2:  # pragma: no cover
85         try:
86             wrapper = view_func.__closure__[0].cell_contents
87         except (AttributeError, IndexError):
88             return None
89     elif sys.version_info[0] == 3:
90         try:
91             wrapper = view_func.__wrapped__
92         except AttributeError:
93             return None
94 
95     if not hasattr(wrapper, "__closure__") or len(wrapper.__closure__) != 2:
96         return None
97 
98     instance = wrapper.__closure__[0].cell_contents
99     if not isinstance(instance, Resource):  # pragma: no cover
100         return None
101 
102     method_name = wrapper.__closure__[1].cell_contents
103     if not isinstance(method_name, string_types):  # pragma: no cover
104         return None
105 
106     if method_name.startswith("dispatch_"):  # pragma: no cover
107         method_name = request.method.lower() + method_name.split("dispatch", 1)[1]
108 
109     return "Controller/{}.{}.{}".format(
110         instance.__module__, instance.__class__.__name__, method_name
111     )
112 
113 
114 def track_request_view_data(request, tracked_request):
115     path = request.path
116     tracked_request.tag(
117         "path",
118         create_filtered_path(
119             path, [(k, v) for k, vs in request.GET.lists() for v in vs]
120         ),
121     )
122     if ignore_path(path):
123         tracked_request.tag("ignore_transaction", True)
124 
125     if scout_config.value("collect_remote_ip"):
126         try:
127             # Determine a remote IP to associate with the request. The value is
128             # spoofable by the requester so this is not suitable to use in any
129             # security sensitive context.
130             user_ip = (
131                 request.META.get("HTTP_X_FORWARDED_FOR", "").split(",")[0]
132                 or request.META.get("HTTP_CLIENT_IP", "").split(",")[0]
133                 or request.META.get("REMOTE_ADDR", None)
134             )
135             tracked_request.tag("user_ip", user_ip)
136         except Exception:
137             pass
138 
139     user = getattr(request, "user", None)
140     if user is not None:
141         try:
142             tracked_request.tag("username", user.get_username())
143         except Exception:
144             pass
145 
146     tracked_request.tag("urlconf", get_urlconf(settings.ROOT_URLCONF))
147 
148 
149 class MiddlewareTimingMiddleware(object):
150     """
151     Insert as early into the Middleware stack as possible (outermost layers),
152     so that other middlewares called after can be timed.
153     """
154 
155     def __init__(self, get_response):
156         self.get_response = get_response
157 
158     def __call__(self, request):
159         if not scout_config.value("monitor"):
160             return self.get_response(request)
161 
162         tracked_request = TrackedRequest.instance()
163 
164         tracked_request.start_span(
165             operation="Middleware", should_capture_backtrace=False
166         )
167         queue_time = request.META.get("HTTP_X_QUEUE_START") or request.META.get(
168             "HTTP_X_REQUEST_START", ""
169         )
170         queue_time_tracked = track_request_queue_time(queue_time, tracked_request)
171         if not queue_time_tracked:
172             track_amazon_request_queue_time(
173                 request.META.get("HTTP_X_AMZN_TRACE_ID", ""), tracked_request
174             )
175 
176         try:
177             return self.get_response(request)
178         finally:
179             tracked_request.stop_span()
180 
181 
182 class ViewTimingMiddleware(object):
183     """
184     Insert as deep into the middleware stack as possible, ideally wrapping no
185     other middleware. Designed to time the View itself
186     """
187 
188     def __init__(self, get_response):
189         self.get_response = get_response
190 
191     def __call__(self, request):
192         """
193         Wrap a single incoming request with start and stop calls.
194         This will start timing, but relies on the process_view callback to
195         capture more details about what view was really called, and other
196         similar info.
197 
198         If process_view isn't called, then the request will not
199         be recorded.  This can happen if a middleware further along the stack
200         doesn't call onward, and instead returns a response directly.
201         """
202         if not scout_config.value("monitor"):
203             return self.get_response(request)
204 
205         tracked_request = TrackedRequest.instance()
206 
207         # This operation name won't be recorded unless changed later in
208         # process_view
209         tracked_request.start_span(operation="Unknown", should_capture_backtrace=False)
210         try:
211             response = self.get_response(request)
212             if 500 <= response.status_code <= 599:
213                 tracked_request.tag("error", "true")
214             return response
215         finally:
216             tracked_request.stop_span()
217 
218     def process_view(self, request, view_func, view_args, view_kwargs):
219         """
220         Capture details about the view_func that is about to execute
221         """
222         if not scout_config.value("monitor"):
223             return
224         tracked_request = TrackedRequest.instance()
225         tracked_request.is_real_request = True
226 
227         track_request_view_data(request, tracked_request)
228 
229         span = tracked_request.current_span()
230         if span is not None:
231             span.operation = get_operation_name(request)
232 
233     def process_exception(self, request, exception):
234         """
235         Mark this request as having errored out
236 
237         Does not modify or catch or otherwise change the exception thrown
238         """
239         if not scout_config.value("monitor"):
240             return
241         TrackedRequest.instance().tag("error", "true")
242 
243 
244 class OldStyleMiddlewareTimingMiddleware(object):
245     """
246     Insert as early into the Middleware stack as possible (outermost layers),
247     so that other middlewares called after can be timed.
248     """
249 
250     def process_request(self, request):
251         if not scout_config.value("monitor"):
252             return
253         tracked_request = TrackedRequest.instance()
254         request._scout_tracked_request = tracked_request
255 
256         queue_time = request.META.get("HTTP_X_QUEUE_START") or request.META.get(
257             "HTTP_X_REQUEST_START", ""
258         )
259         queue_time_tracked = track_request_queue_time(queue_time, tracked_request)
260         if not queue_time_tracked:
261             track_amazon_request_queue_time(
262                 request.META.get("HTTP_X_AMZN_TRACE_ID", ""), tracked_request
263             )
264 
265         tracked_request.start_span(
266             operation="Middleware", should_capture_backtrace=False
267         )
268 
269     def process_response(self, request, response):
270         # Only stop span if there's a request, but presume we are balanced,
271         # i.e. that custom instrumentation within the application is not
272         # causing errors
273         tracked_request = getattr(request, "_scout_tracked_request", None)
274         if 500 <= response.status_code <= 599:
275             tracked_request.tag("error", "true")
276         if tracked_request is not None:
277             tracked_request.stop_span()
278         return response
279 
280 
281 class OldStyleViewMiddleware(object):
282     def process_view(self, request, view_func, view_func_args, view_func_kwargs):
283         tracked_request = getattr(request, "_scout_tracked_request", None)
284         if tracked_request is None:
285             # Looks like OldStyleMiddlewareTimingMiddleware didn't run, so
286             # don't do anything
287             return
288 
289         tracked_request.is_real_request = True
290 
291         track_request_view_data(request, tracked_request)
292 
293         span = tracked_request.start_span(
294             operation=get_operation_name(request), should_capture_backtrace=False
295         )
296         # Save the span into the request, so we can check
297         # if we're matched up when stopping
298         request._scout_view_span = span
299 
300     def process_response(self, request, response):
301         tracked_request = getattr(request, "_scout_tracked_request", None)
302         if tracked_request is None:
303             # Looks like OldStyleMiddlewareTimingMiddleware didn't run, so
304             # don't do anything
305             return response
306 
307         # Only stop span if we started, but presume we are balanced, i.e. that
308         # custom instrumentation within the application is not causing errors
309         span = getattr(request, "_scout_view_span", None)
310         if span is not None:
311             tracked_request.stop_span()
312         return response
313 
314     def process_exception(self, request, exception):
315         tracked_request = getattr(request, "_scout_tracked_request", None)
316         if tracked_request is None:
317             # Looks like OldStyleMiddlewareTimingMiddleware didn't run, so
318             # don't do anything
319             return
320 
321         tracked_request.tag("error", "true")
322
```
--- 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
@@ -136,7 +136,11 @@
         except Exception:
             pass
 
-    user = getattr(request, "user", None)
+    # Django's request.user caches in this attribute on first access. We only
+    # want to track the user if the application code has touched request.user
+    # because touching it causes session access, which adds "Cookie" to the
+    # "Vary" header.
+    user = getattr(request, "_cached_user", None)
     if user is not None:
         try:
             tracked_request.tag("username", user.get_username())
@@ -209,6 +213,7 @@
         tracked_request.start_span(operation="Unknown", should_capture_backtrace=False)
         try:
             response = self.get_response(request)
+            track_request_view_data(request, tracked_request)
             if 500 <= response.status_code <= 599:
                 tracked_request.tag("error", "true")
             return response
@@ -224,8 +229,6 @@
         tracked_request = TrackedRequest.instance()
         tracked_request.is_real_request = True
 
-        track_request_view_data(request, tracked_request)
-
         span = tracked_request.current_span()
         if span is not None:
             span.operation = get_operation_name(request)
@@ -288,8 +291,6 @@
 
         tracked_request.is_real_request = True
 
-        track_request_view_data(request, tracked_request)
-
         span = tracked_request.start_span(
             operation=get_operation_name(request), should_capture_backtrace=False
         )
@@ -304,6 +305,8 @@
             # don't do anything
             return response
 
+        track_request_view_data(request, tracked_request)
+
         # Only stop span if we started, but presume we are balanced, i.e. that
         # custom instrumentation within the application is not causing errors
         span = getattr(request, "_scout_view_span", None)

{"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@@ -136,7 +136,11 @@\n         except Exception:\n             pass\n \n-    user = getattr(request, \"user\", None)\n+    # Django's request.user caches in this attribute on first access. We only\n+    # want to track the user if the application code has touched request.user\n+    # because touching it causes session access, which adds \"Cookie\" to the\n+    # \"Vary\" header.\n+    user = getattr(request, \"_cached_user\", None)\n     if user is not None:\n         try:\n             tracked_request.tag(\"username\", user.get_username())\n@@ -209,6 +213,7 @@\n         tracked_request.start_span(operation=\"Unknown\", should_capture_backtrace=False)\n         try:\n             response = self.get_response(request)\n+            track_request_view_data(request, tracked_request)\n             if 500 <= response.status_code <= 599:\n                 tracked_request.tag(\"error\", \"true\")\n             return response\n@@ -224,8 +229,6 @@\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@@ -288,8 +291,6 @@\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@@ -304,6 +305,8 @@\n             # don't do anything\n             return response\n \n+        track_request_view_data(request, tracked_request)\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", "issue": "scout breaks caching for django\nWe gave scout a try and looked promising but then we quickly had to disable it on production.\r\n\r\nIssue is that scout is adding a `Vary: Cookie` header which breaks caching\r\n\r\n\r\n\r\nThis normally happens when some code in django accesses the request.user object. I'm assuming scout is trying to add some meta data and accesses it causing this issue.\r\n\r\nWe've run into this problem ourselves in the past and the way around is it to look for the internal cached user on the request object. Like this:\r\n\r\n```\r\n    # going request.user will generate cookie vary headers, but since\r\n    # we aren't changing the output based on this we want to see the user\r\n    # without adding the header, so look for the lazy user\r\n    if request and hasattr(request, '_cached_user'):\r\n        # noinspection PyProtectedMember\r\n        user = request._cached_user\r\n```\r\n\r\nI think if replaced the check for user here\r\n\r\nhttps://github.com/scoutapp/scout_apm_python/blob/master/src/scout_apm/django/middleware.py#L139\r\n\r\n\r\n\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    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    if scout_config.value(\"collect_remote_ip\"):\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    if scout_config.value(\"collect_remote_ip\"):\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    # Django's request.user caches in this attribute on first access. We only\n    # want to track the user if the application code has touched request.user\n    # because touching it causes session access, which adds \"Cookie\" to the\n    # \"Vary\" header.\n    user = getattr(request, \"_cached_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            track_request_view_data(request, tracked_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        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        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        track_request_view_data(request, tracked_request)\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"}]}