We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Root Django URL doesn't appear in the UI when using routes
**Describe the bug**: `transaction.name` field is missing from the `transcction` document

**To Reproduce**

1. set `DJANGO_TRANSACTION_NAME_FROM_ROUTE` to `True`
2. have url of `path("", TemplateView.as_view(template_name="pages/home.html"), name="home")`
3. ...
4. not profit

**Environment (please complete the following information)**
- OS: Linux
- Python version: 3.8
- Framework and version [e.g. Django 2.1]: Django 3.1.6
- APM Server version: 7.12
- Agent version:6.1.1

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `elasticapm/contrib/django/middleware/__init__.py`
Content:
```
1 #  BSD 3-Clause License
2 #
3 #  Copyright (c) 2012, the Sentry Team, see AUTHORS for more details
4 #  Copyright (c) 2019, Elasticsearch BV
5 #  All rights reserved.
6 #
7 #  Redistribution and use in source and binary forms, with or without
8 #  modification, are permitted provided that the following conditions are met:
9 #
10 #  * Redistributions of source code must retain the above copyright notice, this
11 #    list of conditions and the following disclaimer.
12 #
13 #  * Redistributions in binary form must reproduce the above copyright notice,
14 #    this list of conditions and the following disclaimer in the documentation
15 #    and/or other materials provided with the distribution.
16 #
17 #  * Neither the name of the copyright holder nor the names of its
18 #    contributors may be used to endorse or promote products derived from
19 #    this software without specific prior written permission.
20 #
21 #  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 #  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 #  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 #  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 #  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 #  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 #  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 #  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 #  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 
31 
32 from __future__ import absolute_import
33 
34 import logging
35 import threading
36 
37 from django.apps import apps
38 from django.conf import settings as django_settings
39 
40 import elasticapm
41 from elasticapm.conf import constants
42 from elasticapm.contrib.django.client import client, get_client
43 from elasticapm.utils import build_name_with_http_method_prefix, get_name_from_func, wrapt
44 
45 try:
46     from importlib import import_module
47 except ImportError:
48     from django.utils.importlib import import_module
49 
50 try:
51     from django.utils.deprecation import MiddlewareMixin
52 except ImportError:
53     # no-op class for Django < 1.10
54     class MiddlewareMixin(object):
55         pass
56 
57 
58 def _is_ignorable_404(uri):
59     """
60     Returns True if the given request *shouldn't* notify the site managers.
61     """
62     urls = getattr(django_settings, "IGNORABLE_404_URLS", ())
63     return any(pattern.search(uri) for pattern in urls)
64 
65 
66 class ElasticAPMClientMiddlewareMixin(object):
67     @property
68     def client(self):
69         try:
70             app = apps.get_app_config("elasticapm.contrib.django")
71             return app.client
72         except LookupError:
73             return get_client()
74 
75 
76 class Catch404Middleware(MiddlewareMixin, ElasticAPMClientMiddlewareMixin):
77     def process_response(self, request, response):
78         if response.status_code != 404 or _is_ignorable_404(request.get_full_path()):
79             return response
80         if django_settings.DEBUG and not self.client.config.debug:
81             return response
82         data = {"level": logging.INFO, "logger": "http404"}
83         result = self.client.capture(
84             "Message",
85             request=request,
86             param_message={"message": "Page Not Found: %s", "params": [request.build_absolute_uri()]},
87             logger_name="http404",
88             level=logging.INFO,
89         )
90         request._elasticapm = {"service_name": data.get("service_name", self.client.config.service_name), "id": result}
91         return response
92 
93 
94 def get_name_from_middleware(wrapped, instance):
95     name = [type(instance).__name__, wrapped.__name__]
96     if type(instance).__module__:
97         name = [type(instance).__module__] + name
98     return ".".join(name)
99 
100 
101 def process_request_wrapper(wrapped, instance, args, kwargs):
102     response = wrapped(*args, **kwargs)
103     try:
104         if response is not None:
105             request = args[0]
106             elasticapm.set_transaction_name(
107                 build_name_with_http_method_prefix(get_name_from_middleware(wrapped, instance), request)
108             )
109     finally:
110         return response
111 
112 
113 def process_response_wrapper(wrapped, instance, args, kwargs):
114     response = wrapped(*args, **kwargs)
115     try:
116         request, original_response = args
117         # if there's no view_func on the request, and this middleware created
118         # a new response object, it's logged as the responsible transaction
119         # name
120         if not hasattr(request, "_elasticapm_view_func") and response is not original_response:
121             elasticapm.set_transaction_name(
122                 build_name_with_http_method_prefix(get_name_from_middleware(wrapped, instance), request)
123             )
124     finally:
125         return response
126 
127 
128 class TracingMiddleware(MiddlewareMixin, ElasticAPMClientMiddlewareMixin):
129     _elasticapm_instrumented = False
130     _instrumenting_lock = threading.Lock()
131 
132     def __init__(self, *args, **kwargs):
133         super(TracingMiddleware, self).__init__(*args, **kwargs)
134         if not self._elasticapm_instrumented:
135             with self._instrumenting_lock:
136                 if not self._elasticapm_instrumented:
137                     if self.client.config.instrument_django_middleware:
138                         self.instrument_middlewares()
139 
140                     TracingMiddleware._elasticapm_instrumented = True
141 
142     def instrument_middlewares(self):
143         middlewares = getattr(django_settings, "MIDDLEWARE", None) or getattr(
144             django_settings, "MIDDLEWARE_CLASSES", None
145         )
146         if middlewares:
147             for middleware_path in middlewares:
148                 module_path, class_name = middleware_path.rsplit(".", 1)
149                 try:
150                     module = import_module(module_path)
151                     middleware_class = getattr(module, class_name)
152                     if middleware_class == type(self):
153                         # don't instrument ourselves
154                         continue
155                     if hasattr(middleware_class, "process_request"):
156                         wrapt.wrap_function_wrapper(middleware_class, "process_request", process_request_wrapper)
157                     if hasattr(middleware_class, "process_response"):
158                         wrapt.wrap_function_wrapper(middleware_class, "process_response", process_response_wrapper)
159                 except ImportError:
160                     client.logger.warning("Can't instrument middleware %s", middleware_path)
161 
162     def process_view(self, request, view_func, view_args, view_kwargs):
163         request._elasticapm_view_func = view_func
164 
165     def process_response(self, request, response):
166         if django_settings.DEBUG and not self.client.config.debug:
167             return response
168         try:
169             if hasattr(response, "status_code"):
170                 transaction_name = None
171                 if self.client.config.django_transaction_name_from_route and hasattr(request.resolver_match, "route"):
172                     transaction_name = request.resolver_match.route
173                 elif getattr(request, "_elasticapm_view_func", False):
174                     transaction_name = get_name_from_func(request._elasticapm_view_func)
175                 if transaction_name:
176                     transaction_name = build_name_with_http_method_prefix(transaction_name, request)
177                     elasticapm.set_transaction_name(transaction_name, override=False)
178 
179                 elasticapm.set_context(
180                     lambda: self.client.get_data_from_request(request, constants.TRANSACTION), "request"
181                 )
182                 elasticapm.set_context(
183                     lambda: self.client.get_data_from_response(response, constants.TRANSACTION), "response"
184                 )
185                 elasticapm.set_context(lambda: self.client.get_user_info(request), "user")
186                 elasticapm.set_transaction_result("HTTP {}xx".format(response.status_code // 100), override=False)
187                 elasticapm.set_transaction_outcome(http_status_code=response.status_code, override=False)
188         except Exception:
189             self.client.error_logger.error("Exception during timing of request", exc_info=True)
190         return response
191 
192 
193 class ErrorIdMiddleware(MiddlewareMixin):
194     """
195     Appends the X-ElasticAPM-ErrorId response header for referencing a message within
196     the ElasticAPM datastore.
197     """
198 
199     def process_response(self, request, response):
200         if not getattr(request, "_elasticapm", None):
201             return response
202         response["X-ElasticAPM-ErrorId"] = request._elasticapm["id"]
203         return response
204 
205 
206 class LogMiddleware(MiddlewareMixin):
207     # Create a thread local variable to store the session in for logging
208     thread = threading.local()
209 
210     def process_request(self, request):
211         self.thread.request = request
212
```
--- 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/elasticapm/contrib/django/middleware/__init__.py b/elasticapm/contrib/django/middleware/__init__.py
--- a/elasticapm/contrib/django/middleware/__init__.py
+++ b/elasticapm/contrib/django/middleware/__init__.py
@@ -169,7 +169,9 @@
             if hasattr(response, "status_code"):
                 transaction_name = None
                 if self.client.config.django_transaction_name_from_route and hasattr(request.resolver_match, "route"):
-                    transaction_name = request.resolver_match.route
+                    r = request.resolver_match
+                    # if no route is defined (e.g. for the root URL), fall back on url_name and then function name
+                    transaction_name = r.route or r.url_name or get_name_from_func(r.func)
                 elif getattr(request, "_elasticapm_view_func", False):
                     transaction_name = get_name_from_func(request._elasticapm_view_func)
                 if transaction_name:

{"golden_diff": "diff --git a/elasticapm/contrib/django/middleware/__init__.py b/elasticapm/contrib/django/middleware/__init__.py\n--- a/elasticapm/contrib/django/middleware/__init__.py\n+++ b/elasticapm/contrib/django/middleware/__init__.py\n@@ -169,7 +169,9 @@\n             if hasattr(response, \"status_code\"):\n                 transaction_name = None\n                 if self.client.config.django_transaction_name_from_route and hasattr(request.resolver_match, \"route\"):\n-                    transaction_name = request.resolver_match.route\n+                    r = request.resolver_match\n+                    # if no route is defined (e.g. for the root URL), fall back on url_name and then function name\n+                    transaction_name = r.route or r.url_name or get_name_from_func(r.func)\n                 elif getattr(request, \"_elasticapm_view_func\", False):\n                     transaction_name = get_name_from_func(request._elasticapm_view_func)\n                 if transaction_name:\n", "issue": "Root Django URL doesn't appear in the UI when using routes\n**Describe the bug**: `transaction.name` field is missing from the `transcction` document\r\n\r\n**To Reproduce**\r\n\r\n1. set `DJANGO_TRANSACTION_NAME_FROM_ROUTE` to `True`\r\n2. have url of `path(\"\", TemplateView.as_view(template_name=\"pages/home.html\"), name=\"home\")`\r\n3. ...\r\n4. not profit\r\n\r\n**Environment (please complete the following information)**\r\n- OS: Linux\r\n- Python version: 3.8\r\n- Framework and version [e.g. Django 2.1]: Django 3.1.6\r\n- APM Server version: 7.12\r\n- Agent version:6.1.1\n", "before_files": [{"content": "#  BSD 3-Clause License\n#\n#  Copyright (c) 2012, the Sentry Team, see AUTHORS for more details\n#  Copyright (c) 2019, Elasticsearch BV\n#  All rights reserved.\n#\n#  Redistribution and use in source and binary forms, with or without\n#  modification, are permitted provided that the following conditions are met:\n#\n#  * Redistributions of source code must retain the above copyright notice, this\n#    list of conditions and the following disclaimer.\n#\n#  * Redistributions in binary form must reproduce the above copyright notice,\n#    this list of conditions and the following disclaimer in the documentation\n#    and/or other materials provided with the distribution.\n#\n#  * Neither the name of the copyright holder nor the names of its\n#    contributors may be used to endorse or promote products derived from\n#    this software without specific prior written permission.\n#\n#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n#  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n#  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n#  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n#  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n#  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n#  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n\n\nfrom __future__ import absolute_import\n\nimport logging\nimport threading\n\nfrom django.apps import apps\nfrom django.conf import settings as django_settings\n\nimport elasticapm\nfrom elasticapm.conf import constants\nfrom elasticapm.contrib.django.client import client, get_client\nfrom elasticapm.utils import build_name_with_http_method_prefix, get_name_from_func, wrapt\n\ntry:\n    from importlib import import_module\nexcept ImportError:\n    from django.utils.importlib import import_module\n\ntry:\n    from django.utils.deprecation import MiddlewareMixin\nexcept ImportError:\n    # no-op class for Django < 1.10\n    class MiddlewareMixin(object):\n        pass\n\n\ndef _is_ignorable_404(uri):\n    \"\"\"\n    Returns True if the given request *shouldn't* notify the site managers.\n    \"\"\"\n    urls = getattr(django_settings, \"IGNORABLE_404_URLS\", ())\n    return any(pattern.search(uri) for pattern in urls)\n\n\nclass ElasticAPMClientMiddlewareMixin(object):\n    @property\n    def client(self):\n        try:\n            app = apps.get_app_config(\"elasticapm.contrib.django\")\n            return app.client\n        except LookupError:\n            return get_client()\n\n\nclass Catch404Middleware(MiddlewareMixin, ElasticAPMClientMiddlewareMixin):\n    def process_response(self, request, response):\n        if response.status_code != 404 or _is_ignorable_404(request.get_full_path()):\n            return response\n        if django_settings.DEBUG and not self.client.config.debug:\n            return response\n        data = {\"level\": logging.INFO, \"logger\": \"http404\"}\n        result = self.client.capture(\n            \"Message\",\n            request=request,\n            param_message={\"message\": \"Page Not Found: %s\", \"params\": [request.build_absolute_uri()]},\n            logger_name=\"http404\",\n            level=logging.INFO,\n        )\n        request._elasticapm = {\"service_name\": data.get(\"service_name\", self.client.config.service_name), \"id\": result}\n        return response\n\n\ndef get_name_from_middleware(wrapped, instance):\n    name = [type(instance).__name__, wrapped.__name__]\n    if type(instance).__module__:\n        name = [type(instance).__module__] + name\n    return \".\".join(name)\n\n\ndef process_request_wrapper(wrapped, instance, args, kwargs):\n    response = wrapped(*args, **kwargs)\n    try:\n        if response is not None:\n            request = args[0]\n            elasticapm.set_transaction_name(\n                build_name_with_http_method_prefix(get_name_from_middleware(wrapped, instance), request)\n            )\n    finally:\n        return response\n\n\ndef process_response_wrapper(wrapped, instance, args, kwargs):\n    response = wrapped(*args, **kwargs)\n    try:\n        request, original_response = args\n        # if there's no view_func on the request, and this middleware created\n        # a new response object, it's logged as the responsible transaction\n        # name\n        if not hasattr(request, \"_elasticapm_view_func\") and response is not original_response:\n            elasticapm.set_transaction_name(\n                build_name_with_http_method_prefix(get_name_from_middleware(wrapped, instance), request)\n            )\n    finally:\n        return response\n\n\nclass TracingMiddleware(MiddlewareMixin, ElasticAPMClientMiddlewareMixin):\n    _elasticapm_instrumented = False\n    _instrumenting_lock = threading.Lock()\n\n    def __init__(self, *args, **kwargs):\n        super(TracingMiddleware, self).__init__(*args, **kwargs)\n        if not self._elasticapm_instrumented:\n            with self._instrumenting_lock:\n                if not self._elasticapm_instrumented:\n                    if self.client.config.instrument_django_middleware:\n                        self.instrument_middlewares()\n\n                    TracingMiddleware._elasticapm_instrumented = True\n\n    def instrument_middlewares(self):\n        middlewares = getattr(django_settings, \"MIDDLEWARE\", None) or getattr(\n            django_settings, \"MIDDLEWARE_CLASSES\", None\n        )\n        if middlewares:\n            for middleware_path in middlewares:\n                module_path, class_name = middleware_path.rsplit(\".\", 1)\n                try:\n                    module = import_module(module_path)\n                    middleware_class = getattr(module, class_name)\n                    if middleware_class == type(self):\n                        # don't instrument ourselves\n                        continue\n                    if hasattr(middleware_class, \"process_request\"):\n                        wrapt.wrap_function_wrapper(middleware_class, \"process_request\", process_request_wrapper)\n                    if hasattr(middleware_class, \"process_response\"):\n                        wrapt.wrap_function_wrapper(middleware_class, \"process_response\", process_response_wrapper)\n                except ImportError:\n                    client.logger.warning(\"Can't instrument middleware %s\", middleware_path)\n\n    def process_view(self, request, view_func, view_args, view_kwargs):\n        request._elasticapm_view_func = view_func\n\n    def process_response(self, request, response):\n        if django_settings.DEBUG and not self.client.config.debug:\n            return response\n        try:\n            if hasattr(response, \"status_code\"):\n                transaction_name = None\n                if self.client.config.django_transaction_name_from_route and hasattr(request.resolver_match, \"route\"):\n                    transaction_name = request.resolver_match.route\n                elif getattr(request, \"_elasticapm_view_func\", False):\n                    transaction_name = get_name_from_func(request._elasticapm_view_func)\n                if transaction_name:\n                    transaction_name = build_name_with_http_method_prefix(transaction_name, request)\n                    elasticapm.set_transaction_name(transaction_name, override=False)\n\n                elasticapm.set_context(\n                    lambda: self.client.get_data_from_request(request, constants.TRANSACTION), \"request\"\n                )\n                elasticapm.set_context(\n                    lambda: self.client.get_data_from_response(response, constants.TRANSACTION), \"response\"\n                )\n                elasticapm.set_context(lambda: self.client.get_user_info(request), \"user\")\n                elasticapm.set_transaction_result(\"HTTP {}xx\".format(response.status_code // 100), override=False)\n                elasticapm.set_transaction_outcome(http_status_code=response.status_code, override=False)\n        except Exception:\n            self.client.error_logger.error(\"Exception during timing of request\", exc_info=True)\n        return response\n\n\nclass ErrorIdMiddleware(MiddlewareMixin):\n    \"\"\"\n    Appends the X-ElasticAPM-ErrorId response header for referencing a message within\n    the ElasticAPM datastore.\n    \"\"\"\n\n    def process_response(self, request, response):\n        if not getattr(request, \"_elasticapm\", None):\n            return response\n        response[\"X-ElasticAPM-ErrorId\"] = request._elasticapm[\"id\"]\n        return response\n\n\nclass LogMiddleware(MiddlewareMixin):\n    # Create a thread local variable to store the session in for logging\n    thread = threading.local()\n\n    def process_request(self, request):\n        self.thread.request = request\n", "path": "elasticapm/contrib/django/middleware/__init__.py"}], "after_files": [{"content": "#  BSD 3-Clause License\n#\n#  Copyright (c) 2012, the Sentry Team, see AUTHORS for more details\n#  Copyright (c) 2019, Elasticsearch BV\n#  All rights reserved.\n#\n#  Redistribution and use in source and binary forms, with or without\n#  modification, are permitted provided that the following conditions are met:\n#\n#  * Redistributions of source code must retain the above copyright notice, this\n#    list of conditions and the following disclaimer.\n#\n#  * Redistributions in binary form must reproduce the above copyright notice,\n#    this list of conditions and the following disclaimer in the documentation\n#    and/or other materials provided with the distribution.\n#\n#  * Neither the name of the copyright holder nor the names of its\n#    contributors may be used to endorse or promote products derived from\n#    this software without specific prior written permission.\n#\n#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n#  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n#  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n#  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n#  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n#  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n#  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n\n\nfrom __future__ import absolute_import\n\nimport logging\nimport threading\n\nfrom django.apps import apps\nfrom django.conf import settings as django_settings\n\nimport elasticapm\nfrom elasticapm.conf import constants\nfrom elasticapm.contrib.django.client import client, get_client\nfrom elasticapm.utils import build_name_with_http_method_prefix, get_name_from_func, wrapt\n\ntry:\n    from importlib import import_module\nexcept ImportError:\n    from django.utils.importlib import import_module\n\ntry:\n    from django.utils.deprecation import MiddlewareMixin\nexcept ImportError:\n    # no-op class for Django < 1.10\n    class MiddlewareMixin(object):\n        pass\n\n\ndef _is_ignorable_404(uri):\n    \"\"\"\n    Returns True if the given request *shouldn't* notify the site managers.\n    \"\"\"\n    urls = getattr(django_settings, \"IGNORABLE_404_URLS\", ())\n    return any(pattern.search(uri) for pattern in urls)\n\n\nclass ElasticAPMClientMiddlewareMixin(object):\n    @property\n    def client(self):\n        try:\n            app = apps.get_app_config(\"elasticapm.contrib.django\")\n            return app.client\n        except LookupError:\n            return get_client()\n\n\nclass Catch404Middleware(MiddlewareMixin, ElasticAPMClientMiddlewareMixin):\n    def process_response(self, request, response):\n        if response.status_code != 404 or _is_ignorable_404(request.get_full_path()):\n            return response\n        if django_settings.DEBUG and not self.client.config.debug:\n            return response\n        data = {\"level\": logging.INFO, \"logger\": \"http404\"}\n        result = self.client.capture(\n            \"Message\",\n            request=request,\n            param_message={\"message\": \"Page Not Found: %s\", \"params\": [request.build_absolute_uri()]},\n            logger_name=\"http404\",\n            level=logging.INFO,\n        )\n        request._elasticapm = {\"service_name\": data.get(\"service_name\", self.client.config.service_name), \"id\": result}\n        return response\n\n\ndef get_name_from_middleware(wrapped, instance):\n    name = [type(instance).__name__, wrapped.__name__]\n    if type(instance).__module__:\n        name = [type(instance).__module__] + name\n    return \".\".join(name)\n\n\ndef process_request_wrapper(wrapped, instance, args, kwargs):\n    response = wrapped(*args, **kwargs)\n    try:\n        if response is not None:\n            request = args[0]\n            elasticapm.set_transaction_name(\n                build_name_with_http_method_prefix(get_name_from_middleware(wrapped, instance), request)\n            )\n    finally:\n        return response\n\n\ndef process_response_wrapper(wrapped, instance, args, kwargs):\n    response = wrapped(*args, **kwargs)\n    try:\n        request, original_response = args\n        # if there's no view_func on the request, and this middleware created\n        # a new response object, it's logged as the responsible transaction\n        # name\n        if not hasattr(request, \"_elasticapm_view_func\") and response is not original_response:\n            elasticapm.set_transaction_name(\n                build_name_with_http_method_prefix(get_name_from_middleware(wrapped, instance), request)\n            )\n    finally:\n        return response\n\n\nclass TracingMiddleware(MiddlewareMixin, ElasticAPMClientMiddlewareMixin):\n    _elasticapm_instrumented = False\n    _instrumenting_lock = threading.Lock()\n\n    def __init__(self, *args, **kwargs):\n        super(TracingMiddleware, self).__init__(*args, **kwargs)\n        if not self._elasticapm_instrumented:\n            with self._instrumenting_lock:\n                if not self._elasticapm_instrumented:\n                    if self.client.config.instrument_django_middleware:\n                        self.instrument_middlewares()\n\n                    TracingMiddleware._elasticapm_instrumented = True\n\n    def instrument_middlewares(self):\n        middlewares = getattr(django_settings, \"MIDDLEWARE\", None) or getattr(\n            django_settings, \"MIDDLEWARE_CLASSES\", None\n        )\n        if middlewares:\n            for middleware_path in middlewares:\n                module_path, class_name = middleware_path.rsplit(\".\", 1)\n                try:\n                    module = import_module(module_path)\n                    middleware_class = getattr(module, class_name)\n                    if middleware_class == type(self):\n                        # don't instrument ourselves\n                        continue\n                    if hasattr(middleware_class, \"process_request\"):\n                        wrapt.wrap_function_wrapper(middleware_class, \"process_request\", process_request_wrapper)\n                    if hasattr(middleware_class, \"process_response\"):\n                        wrapt.wrap_function_wrapper(middleware_class, \"process_response\", process_response_wrapper)\n                except ImportError:\n                    client.logger.warning(\"Can't instrument middleware %s\", middleware_path)\n\n    def process_view(self, request, view_func, view_args, view_kwargs):\n        request._elasticapm_view_func = view_func\n\n    def process_response(self, request, response):\n        if django_settings.DEBUG and not self.client.config.debug:\n            return response\n        try:\n            if hasattr(response, \"status_code\"):\n                transaction_name = None\n                if self.client.config.django_transaction_name_from_route and hasattr(request.resolver_match, \"route\"):\n                    r = request.resolver_match\n                    # if no route is defined (e.g. for the root URL), fall back on url_name and then function name\n                    transaction_name = r.route or r.url_name or get_name_from_func(r.func)\n                elif getattr(request, \"_elasticapm_view_func\", False):\n                    transaction_name = get_name_from_func(request._elasticapm_view_func)\n                if transaction_name:\n                    transaction_name = build_name_with_http_method_prefix(transaction_name, request)\n                    elasticapm.set_transaction_name(transaction_name, override=False)\n\n                elasticapm.set_context(\n                    lambda: self.client.get_data_from_request(request, constants.TRANSACTION), \"request\"\n                )\n                elasticapm.set_context(\n                    lambda: self.client.get_data_from_response(response, constants.TRANSACTION), \"response\"\n                )\n                elasticapm.set_context(lambda: self.client.get_user_info(request), \"user\")\n                elasticapm.set_transaction_result(\"HTTP {}xx\".format(response.status_code // 100), override=False)\n                elasticapm.set_transaction_outcome(http_status_code=response.status_code, override=False)\n        except Exception:\n            self.client.error_logger.error(\"Exception during timing of request\", exc_info=True)\n        return response\n\n\nclass ErrorIdMiddleware(MiddlewareMixin):\n    \"\"\"\n    Appends the X-ElasticAPM-ErrorId response header for referencing a message within\n    the ElasticAPM datastore.\n    \"\"\"\n\n    def process_response(self, request, response):\n        if not getattr(request, \"_elasticapm\", None):\n            return response\n        response[\"X-ElasticAPM-ErrorId\"] = request._elasticapm[\"id\"]\n        return response\n\n\nclass LogMiddleware(MiddlewareMixin):\n    # Create a thread local variable to store the session in for logging\n    thread = threading.local()\n\n    def process_request(self, request):\n        self.thread.request = request\n", "path": "elasticapm/contrib/django/middleware/__init__.py"}]}