We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Flask integration doesn't send transaction for unhandled errors
**Describe the bug**: When an unhandled error occurs, the transaction is not ended correctly, and no transaction data is captured

**To Reproduce**

1. Access a flask endpoint that triggers an error
2. Error is captured, transaction is not

**Expected behavior**: Both error and transaction events should be captured

This is due to the fact that we listen to the `flask.signals.request_finished` signal, which is not sent when an error occurs. So far, I haven't found a better candidate

 * `request_teardown` is sent even if an error occurs, but it isn't provided the `response` object, which we need
 * similarly, `got_request_exception` also doesn't have the `response` object
 * functions decorated with `after_request` are also not called if an unhandled exception is triggered

We can move some of the work we do currently do in the `request_finished` handler to the `request_started` handler. But for setting the transaction result (based on the HTTP status code), we *need* the response object. I currently don't see a way to do this without taking out the big monkeypatching guns :( 

Flask integration doesn't send transaction for unhandled errors
**Describe the bug**: When an unhandled error occurs, the transaction is not ended correctly, and no transaction data is captured

**To Reproduce**

1. Access a flask endpoint that triggers an error
2. Error is captured, transaction is not

**Expected behavior**: Both error and transaction events should be captured

This is due to the fact that we listen to the `flask.signals.request_finished` signal, which is not sent when an error occurs. So far, I haven't found a better candidate

 * `request_teardown` is sent even if an error occurs, but it isn't provided the `response` object, which we need
 * similarly, `got_request_exception` also doesn't have the `response` object
 * functions decorated with `after_request` are also not called if an unhandled exception is triggered

We can move some of the work we do currently do in the `request_finished` handler to the `request_started` handler. But for setting the transaction result (based on the HTTP status code), we *need* the response object. I currently don't see a way to do this without taking out the big monkeypatching guns :( 


--- 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/flask/__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 flask
35 from flask import request, signals
36 
37 import elasticapm
38 import elasticapm.instrumentation.control
39 from elasticapm.base import Client
40 from elasticapm.conf import constants, setup_logging
41 from elasticapm.contrib.flask.utils import get_data_from_request, get_data_from_response
42 from elasticapm.handlers.logging import LoggingHandler
43 from elasticapm.traces import execution_context
44 from elasticapm.utils import build_name_with_http_method_prefix
45 from elasticapm.utils.disttracing import TraceParent
46 from elasticapm.utils.logging import get_logger
47 
48 logger = get_logger("elasticapm.errors.client")
49 
50 
51 def make_client(client_cls, app, **defaults):
52     config = app.config.get("ELASTIC_APM", {})
53 
54     if "framework_name" not in defaults:
55         defaults["framework_name"] = "flask"
56         defaults["framework_version"] = getattr(flask, "__version__", "<0.7")
57 
58     client = client_cls(config, **defaults)
59     return client
60 
61 
62 class ElasticAPM(object):
63     """
64     Flask application for Elastic APM.
65 
66     Look up configuration from ``os.environ.get('ELASTIC_APM_APP_NAME')`` and
67     ``os.environ.get('ELASTIC_APM_SECRET_TOKEN')``::
68 
69     >>> elasticapm = ElasticAPM(app)
70 
71     Pass an arbitrary APP_NAME and SECRET_TOKEN::
72 
73     >>> elasticapm = ElasticAPM(app, service_name='myapp', secret_token='asdasdasd')
74 
75     Pass an explicit client::
76 
77     >>> elasticapm = ElasticAPM(app, client=client)
78 
79     Automatically configure logging::
80 
81     >>> elasticapm = ElasticAPM(app, logging=True)
82 
83     Capture an exception::
84 
85     >>> try:
86     >>>     1 / 0
87     >>> except ZeroDivisionError:
88     >>>     elasticapm.capture_exception()
89 
90     Capture a message::
91 
92     >>> elasticapm.capture_message('hello, world!')
93     """
94 
95     def __init__(self, app=None, client=None, client_cls=Client, logging=False, **defaults):
96         self.app = app
97         self.logging = logging
98         self.client_cls = client_cls
99         self.client = client
100 
101         if app:
102             self.init_app(app, **defaults)
103 
104     def handle_exception(self, *args, **kwargs):
105         if not self.client:
106             return
107 
108         if self.app.debug and not self.client.config.debug:
109             return
110 
111         self.client.capture_exception(
112             exc_info=kwargs.get("exc_info"),
113             context={
114                 "request": get_data_from_request(
115                     request,
116                     capture_body=self.client.config.capture_body in ("errors", "all"),
117                     capture_headers=self.client.config.capture_headers,
118                 )
119             },
120             custom={"app": self.app},
121             handled=False,
122         )
123 
124     def init_app(self, app, **defaults):
125         self.app = app
126         if not self.client:
127             self.client = make_client(self.client_cls, app, **defaults)
128 
129         # 0 is a valid log level (NOTSET), so we need to check explicitly for it
130         if self.logging or self.logging is 0:  # noqa F632
131             if self.logging is not True:
132                 kwargs = {"level": self.logging}
133             else:
134                 kwargs = {}
135             setup_logging(LoggingHandler(self.client, **kwargs))
136 
137         signals.got_request_exception.connect(self.handle_exception, sender=app, weak=False)
138 
139         try:
140             from elasticapm.contrib.celery import register_exception_tracking
141 
142             register_exception_tracking(self.client)
143         except ImportError:
144             pass
145 
146         # Instrument to get spans
147         if self.client.config.instrument:
148             elasticapm.instrumentation.control.instrument()
149 
150             signals.request_started.connect(self.request_started, sender=app)
151             signals.request_finished.connect(self.request_finished, sender=app)
152             try:
153                 from elasticapm.contrib.celery import register_instrumentation
154 
155                 register_instrumentation(self.client)
156             except ImportError:
157                 pass
158         else:
159             logger.debug("Skipping instrumentation. INSTRUMENT is set to False.")
160 
161         @app.context_processor
162         def rum_tracing():
163             """
164             Adds APM related IDs to the context used for correlating the backend transaction with the RUM transaction
165             """
166             transaction = execution_context.get_transaction()
167             if transaction and transaction.trace_parent:
168                 return {
169                     "apm": {
170                         "trace_id": transaction.trace_parent.trace_id,
171                         "span_id": lambda: transaction.ensure_parent_id(),
172                         "is_sampled": transaction.is_sampled,
173                         "is_sampled_js": "true" if transaction.is_sampled else "false",
174                     }
175                 }
176             return {}
177 
178     def request_started(self, app):
179         if not self.app.debug or self.client.config.debug:
180             if constants.TRACEPARENT_HEADER_NAME in request.headers:
181                 trace_parent = TraceParent.from_string(request.headers[constants.TRACEPARENT_HEADER_NAME])
182             else:
183                 trace_parent = None
184             self.client.begin_transaction("request", trace_parent=trace_parent)
185 
186     def request_finished(self, app, response):
187         if not self.app.debug or self.client.config.debug:
188             rule = request.url_rule.rule if request.url_rule is not None else ""
189             rule = build_name_with_http_method_prefix(rule, request)
190             elasticapm.set_context(
191                 lambda: get_data_from_request(
192                     request,
193                     capture_body=self.client.config.capture_body in ("transactions", "all"),
194                     capture_headers=self.client.config.capture_headers,
195                 ),
196                 "request",
197             )
198             elasticapm.set_context(
199                 lambda: get_data_from_response(response, capture_headers=self.client.config.capture_headers), "response"
200             )
201             if response.status_code:
202                 result = "HTTP {}xx".format(response.status_code // 100)
203             else:
204                 result = response.status
205             elasticapm.set_transaction_name(rule, override=False)
206             elasticapm.set_transaction_result(result, override=False)
207             # Instead of calling end_transaction here, we defer the call until the response is closed.
208             # This ensures that we capture things that happen until the WSGI server closes the response.
209             response.call_on_close(self.client.end_transaction)
210 
211     def capture_exception(self, *args, **kwargs):
212         assert self.client, "capture_exception called before application configured"
213         return self.client.capture_exception(*args, **kwargs)
214 
215     def capture_message(self, *args, **kwargs):
216         assert self.client, "capture_message called before application configured"
217         return self.client.capture_message(*args, **kwargs)
218
```
--- 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/flask/__init__.py b/elasticapm/contrib/flask/__init__.py
--- a/elasticapm/contrib/flask/__init__.py
+++ b/elasticapm/contrib/flask/__init__.py
@@ -120,6 +120,12 @@
             custom={"app": self.app},
             handled=False,
         )
+        # End the transaction here, as `request_finished` won't be called when an
+        # unhandled exception occurs.
+        #
+        # Unfortunately, that also means that we can't capture any response data,
+        # as the response isn't ready at this point in time.
+        self.client.end_transaction(result="HTTP 5xx")
 
     def init_app(self, app, **defaults):
         self.app = app
@@ -182,11 +188,6 @@
             else:
                 trace_parent = None
             self.client.begin_transaction("request", trace_parent=trace_parent)
-
-    def request_finished(self, app, response):
-        if not self.app.debug or self.client.config.debug:
-            rule = request.url_rule.rule if request.url_rule is not None else ""
-            rule = build_name_with_http_method_prefix(rule, request)
             elasticapm.set_context(
                 lambda: get_data_from_request(
                     request,
@@ -195,6 +196,12 @@
                 ),
                 "request",
             )
+            rule = request.url_rule.rule if request.url_rule is not None else ""
+            rule = build_name_with_http_method_prefix(rule, request)
+            elasticapm.set_transaction_name(rule, override=False)
+
+    def request_finished(self, app, response):
+        if not self.app.debug or self.client.config.debug:
             elasticapm.set_context(
                 lambda: get_data_from_response(response, capture_headers=self.client.config.capture_headers), "response"
             )
@@ -202,7 +209,6 @@
                 result = "HTTP {}xx".format(response.status_code // 100)
             else:
                 result = response.status
-            elasticapm.set_transaction_name(rule, override=False)
             elasticapm.set_transaction_result(result, override=False)
             # Instead of calling end_transaction here, we defer the call until the response is closed.
             # This ensures that we capture things that happen until the WSGI server closes the response.

{"golden_diff": "diff --git a/elasticapm/contrib/flask/__init__.py b/elasticapm/contrib/flask/__init__.py\n--- a/elasticapm/contrib/flask/__init__.py\n+++ b/elasticapm/contrib/flask/__init__.py\n@@ -120,6 +120,12 @@\n             custom={\"app\": self.app},\n             handled=False,\n         )\n+        # End the transaction here, as `request_finished` won't be called when an\n+        # unhandled exception occurs.\n+        #\n+        # Unfortunately, that also means that we can't capture any response data,\n+        # as the response isn't ready at this point in time.\n+        self.client.end_transaction(result=\"HTTP 5xx\")\n \n     def init_app(self, app, **defaults):\n         self.app = app\n@@ -182,11 +188,6 @@\n             else:\n                 trace_parent = None\n             self.client.begin_transaction(\"request\", trace_parent=trace_parent)\n-\n-    def request_finished(self, app, response):\n-        if not self.app.debug or self.client.config.debug:\n-            rule = request.url_rule.rule if request.url_rule is not None else \"\"\n-            rule = build_name_with_http_method_prefix(rule, request)\n             elasticapm.set_context(\n                 lambda: get_data_from_request(\n                     request,\n@@ -195,6 +196,12 @@\n                 ),\n                 \"request\",\n             )\n+            rule = request.url_rule.rule if request.url_rule is not None else \"\"\n+            rule = build_name_with_http_method_prefix(rule, request)\n+            elasticapm.set_transaction_name(rule, override=False)\n+\n+    def request_finished(self, app, response):\n+        if not self.app.debug or self.client.config.debug:\n             elasticapm.set_context(\n                 lambda: get_data_from_response(response, capture_headers=self.client.config.capture_headers), \"response\"\n             )\n@@ -202,7 +209,6 @@\n                 result = \"HTTP {}xx\".format(response.status_code // 100)\n             else:\n                 result = response.status\n-            elasticapm.set_transaction_name(rule, override=False)\n             elasticapm.set_transaction_result(result, override=False)\n             # Instead of calling end_transaction here, we defer the call until the response is closed.\n             # This ensures that we capture things that happen until the WSGI server closes the response.\n", "issue": "Flask integration doesn't send transaction for unhandled errors\n**Describe the bug**: When an unhandled error occurs, the transaction is not ended correctly, and no transaction data is captured\r\n\r\n**To Reproduce**\r\n\r\n1. Access a flask endpoint that triggers an error\r\n2. Error is captured, transaction is not\r\n\r\n**Expected behavior**: Both error and transaction events should be captured\r\n\r\nThis is due to the fact that we listen to the `flask.signals.request_finished` signal, which is not sent when an error occurs. So far, I haven't found a better candidate\r\n\r\n * `request_teardown` is sent even if an error occurs, but it isn't provided the `response` object, which we need\r\n * similarly, `got_request_exception` also doesn't have the `response` object\r\n * functions decorated with `after_request` are also not called if an unhandled exception is triggered\r\n\r\nWe can move some of the work we do currently do in the `request_finished` handler to the `request_started` handler. But for setting the transaction result (based on the HTTP status code), we *need* the response object. I currently don't see a way to do this without taking out the big monkeypatching guns :( \r\n\nFlask integration doesn't send transaction for unhandled errors\n**Describe the bug**: When an unhandled error occurs, the transaction is not ended correctly, and no transaction data is captured\r\n\r\n**To Reproduce**\r\n\r\n1. Access a flask endpoint that triggers an error\r\n2. Error is captured, transaction is not\r\n\r\n**Expected behavior**: Both error and transaction events should be captured\r\n\r\nThis is due to the fact that we listen to the `flask.signals.request_finished` signal, which is not sent when an error occurs. So far, I haven't found a better candidate\r\n\r\n * `request_teardown` is sent even if an error occurs, but it isn't provided the `response` object, which we need\r\n * similarly, `got_request_exception` also doesn't have the `response` object\r\n * functions decorated with `after_request` are also not called if an unhandled exception is triggered\r\n\r\nWe can move some of the work we do currently do in the `request_finished` handler to the `request_started` handler. But for setting the transaction result (based on the HTTP status code), we *need* the response object. I currently don't see a way to do this without taking out the big monkeypatching guns :( \r\n\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 flask\nfrom flask import request, signals\n\nimport elasticapm\nimport elasticapm.instrumentation.control\nfrom elasticapm.base import Client\nfrom elasticapm.conf import constants, setup_logging\nfrom elasticapm.contrib.flask.utils import get_data_from_request, get_data_from_response\nfrom elasticapm.handlers.logging import LoggingHandler\nfrom elasticapm.traces import execution_context\nfrom elasticapm.utils import build_name_with_http_method_prefix\nfrom elasticapm.utils.disttracing import TraceParent\nfrom elasticapm.utils.logging import get_logger\n\nlogger = get_logger(\"elasticapm.errors.client\")\n\n\ndef make_client(client_cls, app, **defaults):\n    config = app.config.get(\"ELASTIC_APM\", {})\n\n    if \"framework_name\" not in defaults:\n        defaults[\"framework_name\"] = \"flask\"\n        defaults[\"framework_version\"] = getattr(flask, \"__version__\", \"<0.7\")\n\n    client = client_cls(config, **defaults)\n    return client\n\n\nclass ElasticAPM(object):\n    \"\"\"\n    Flask application for Elastic APM.\n\n    Look up configuration from ``os.environ.get('ELASTIC_APM_APP_NAME')`` and\n    ``os.environ.get('ELASTIC_APM_SECRET_TOKEN')``::\n\n    >>> elasticapm = ElasticAPM(app)\n\n    Pass an arbitrary APP_NAME and SECRET_TOKEN::\n\n    >>> elasticapm = ElasticAPM(app, service_name='myapp', secret_token='asdasdasd')\n\n    Pass an explicit client::\n\n    >>> elasticapm = ElasticAPM(app, client=client)\n\n    Automatically configure logging::\n\n    >>> elasticapm = ElasticAPM(app, logging=True)\n\n    Capture an exception::\n\n    >>> try:\n    >>>     1 / 0\n    >>> except ZeroDivisionError:\n    >>>     elasticapm.capture_exception()\n\n    Capture a message::\n\n    >>> elasticapm.capture_message('hello, world!')\n    \"\"\"\n\n    def __init__(self, app=None, client=None, client_cls=Client, logging=False, **defaults):\n        self.app = app\n        self.logging = logging\n        self.client_cls = client_cls\n        self.client = client\n\n        if app:\n            self.init_app(app, **defaults)\n\n    def handle_exception(self, *args, **kwargs):\n        if not self.client:\n            return\n\n        if self.app.debug and not self.client.config.debug:\n            return\n\n        self.client.capture_exception(\n            exc_info=kwargs.get(\"exc_info\"),\n            context={\n                \"request\": get_data_from_request(\n                    request,\n                    capture_body=self.client.config.capture_body in (\"errors\", \"all\"),\n                    capture_headers=self.client.config.capture_headers,\n                )\n            },\n            custom={\"app\": self.app},\n            handled=False,\n        )\n\n    def init_app(self, app, **defaults):\n        self.app = app\n        if not self.client:\n            self.client = make_client(self.client_cls, app, **defaults)\n\n        # 0 is a valid log level (NOTSET), so we need to check explicitly for it\n        if self.logging or self.logging is 0:  # noqa F632\n            if self.logging is not True:\n                kwargs = {\"level\": self.logging}\n            else:\n                kwargs = {}\n            setup_logging(LoggingHandler(self.client, **kwargs))\n\n        signals.got_request_exception.connect(self.handle_exception, sender=app, weak=False)\n\n        try:\n            from elasticapm.contrib.celery import register_exception_tracking\n\n            register_exception_tracking(self.client)\n        except ImportError:\n            pass\n\n        # Instrument to get spans\n        if self.client.config.instrument:\n            elasticapm.instrumentation.control.instrument()\n\n            signals.request_started.connect(self.request_started, sender=app)\n            signals.request_finished.connect(self.request_finished, sender=app)\n            try:\n                from elasticapm.contrib.celery import register_instrumentation\n\n                register_instrumentation(self.client)\n            except ImportError:\n                pass\n        else:\n            logger.debug(\"Skipping instrumentation. INSTRUMENT is set to False.\")\n\n        @app.context_processor\n        def rum_tracing():\n            \"\"\"\n            Adds APM related IDs to the context used for correlating the backend transaction with the RUM transaction\n            \"\"\"\n            transaction = execution_context.get_transaction()\n            if transaction and transaction.trace_parent:\n                return {\n                    \"apm\": {\n                        \"trace_id\": transaction.trace_parent.trace_id,\n                        \"span_id\": lambda: transaction.ensure_parent_id(),\n                        \"is_sampled\": transaction.is_sampled,\n                        \"is_sampled_js\": \"true\" if transaction.is_sampled else \"false\",\n                    }\n                }\n            return {}\n\n    def request_started(self, app):\n        if not self.app.debug or self.client.config.debug:\n            if constants.TRACEPARENT_HEADER_NAME in request.headers:\n                trace_parent = TraceParent.from_string(request.headers[constants.TRACEPARENT_HEADER_NAME])\n            else:\n                trace_parent = None\n            self.client.begin_transaction(\"request\", trace_parent=trace_parent)\n\n    def request_finished(self, app, response):\n        if not self.app.debug or self.client.config.debug:\n            rule = request.url_rule.rule if request.url_rule is not None else \"\"\n            rule = build_name_with_http_method_prefix(rule, request)\n            elasticapm.set_context(\n                lambda: get_data_from_request(\n                    request,\n                    capture_body=self.client.config.capture_body in (\"transactions\", \"all\"),\n                    capture_headers=self.client.config.capture_headers,\n                ),\n                \"request\",\n            )\n            elasticapm.set_context(\n                lambda: get_data_from_response(response, capture_headers=self.client.config.capture_headers), \"response\"\n            )\n            if response.status_code:\n                result = \"HTTP {}xx\".format(response.status_code // 100)\n            else:\n                result = response.status\n            elasticapm.set_transaction_name(rule, override=False)\n            elasticapm.set_transaction_result(result, override=False)\n            # Instead of calling end_transaction here, we defer the call until the response is closed.\n            # This ensures that we capture things that happen until the WSGI server closes the response.\n            response.call_on_close(self.client.end_transaction)\n\n    def capture_exception(self, *args, **kwargs):\n        assert self.client, \"capture_exception called before application configured\"\n        return self.client.capture_exception(*args, **kwargs)\n\n    def capture_message(self, *args, **kwargs):\n        assert self.client, \"capture_message called before application configured\"\n        return self.client.capture_message(*args, **kwargs)\n", "path": "elasticapm/contrib/flask/__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 flask\nfrom flask import request, signals\n\nimport elasticapm\nimport elasticapm.instrumentation.control\nfrom elasticapm.base import Client\nfrom elasticapm.conf import constants, setup_logging\nfrom elasticapm.contrib.flask.utils import get_data_from_request, get_data_from_response\nfrom elasticapm.handlers.logging import LoggingHandler\nfrom elasticapm.traces import execution_context\nfrom elasticapm.utils import build_name_with_http_method_prefix\nfrom elasticapm.utils.disttracing import TraceParent\nfrom elasticapm.utils.logging import get_logger\n\nlogger = get_logger(\"elasticapm.errors.client\")\n\n\ndef make_client(client_cls, app, **defaults):\n    config = app.config.get(\"ELASTIC_APM\", {})\n\n    if \"framework_name\" not in defaults:\n        defaults[\"framework_name\"] = \"flask\"\n        defaults[\"framework_version\"] = getattr(flask, \"__version__\", \"<0.7\")\n\n    client = client_cls(config, **defaults)\n    return client\n\n\nclass ElasticAPM(object):\n    \"\"\"\n    Flask application for Elastic APM.\n\n    Look up configuration from ``os.environ.get('ELASTIC_APM_APP_NAME')`` and\n    ``os.environ.get('ELASTIC_APM_SECRET_TOKEN')``::\n\n    >>> elasticapm = ElasticAPM(app)\n\n    Pass an arbitrary APP_NAME and SECRET_TOKEN::\n\n    >>> elasticapm = ElasticAPM(app, service_name='myapp', secret_token='asdasdasd')\n\n    Pass an explicit client::\n\n    >>> elasticapm = ElasticAPM(app, client=client)\n\n    Automatically configure logging::\n\n    >>> elasticapm = ElasticAPM(app, logging=True)\n\n    Capture an exception::\n\n    >>> try:\n    >>>     1 / 0\n    >>> except ZeroDivisionError:\n    >>>     elasticapm.capture_exception()\n\n    Capture a message::\n\n    >>> elasticapm.capture_message('hello, world!')\n    \"\"\"\n\n    def __init__(self, app=None, client=None, client_cls=Client, logging=False, **defaults):\n        self.app = app\n        self.logging = logging\n        self.client_cls = client_cls\n        self.client = client\n\n        if app:\n            self.init_app(app, **defaults)\n\n    def handle_exception(self, *args, **kwargs):\n        if not self.client:\n            return\n\n        if self.app.debug and not self.client.config.debug:\n            return\n\n        self.client.capture_exception(\n            exc_info=kwargs.get(\"exc_info\"),\n            context={\n                \"request\": get_data_from_request(\n                    request,\n                    capture_body=self.client.config.capture_body in (\"errors\", \"all\"),\n                    capture_headers=self.client.config.capture_headers,\n                )\n            },\n            custom={\"app\": self.app},\n            handled=False,\n        )\n        # End the transaction here, as `request_finished` won't be called when an\n        # unhandled exception occurs.\n        #\n        # Unfortunately, that also means that we can't capture any response data,\n        # as the response isn't ready at this point in time.\n        self.client.end_transaction(result=\"HTTP 5xx\")\n\n    def init_app(self, app, **defaults):\n        self.app = app\n        if not self.client:\n            self.client = make_client(self.client_cls, app, **defaults)\n\n        # 0 is a valid log level (NOTSET), so we need to check explicitly for it\n        if self.logging or self.logging is 0:  # noqa F632\n            if self.logging is not True:\n                kwargs = {\"level\": self.logging}\n            else:\n                kwargs = {}\n            setup_logging(LoggingHandler(self.client, **kwargs))\n\n        signals.got_request_exception.connect(self.handle_exception, sender=app, weak=False)\n\n        try:\n            from elasticapm.contrib.celery import register_exception_tracking\n\n            register_exception_tracking(self.client)\n        except ImportError:\n            pass\n\n        # Instrument to get spans\n        if self.client.config.instrument:\n            elasticapm.instrumentation.control.instrument()\n\n            signals.request_started.connect(self.request_started, sender=app)\n            signals.request_finished.connect(self.request_finished, sender=app)\n            try:\n                from elasticapm.contrib.celery import register_instrumentation\n\n                register_instrumentation(self.client)\n            except ImportError:\n                pass\n        else:\n            logger.debug(\"Skipping instrumentation. INSTRUMENT is set to False.\")\n\n        @app.context_processor\n        def rum_tracing():\n            \"\"\"\n            Adds APM related IDs to the context used for correlating the backend transaction with the RUM transaction\n            \"\"\"\n            transaction = execution_context.get_transaction()\n            if transaction and transaction.trace_parent:\n                return {\n                    \"apm\": {\n                        \"trace_id\": transaction.trace_parent.trace_id,\n                        \"span_id\": lambda: transaction.ensure_parent_id(),\n                        \"is_sampled\": transaction.is_sampled,\n                        \"is_sampled_js\": \"true\" if transaction.is_sampled else \"false\",\n                    }\n                }\n            return {}\n\n    def request_started(self, app):\n        if not self.app.debug or self.client.config.debug:\n            if constants.TRACEPARENT_HEADER_NAME in request.headers:\n                trace_parent = TraceParent.from_string(request.headers[constants.TRACEPARENT_HEADER_NAME])\n            else:\n                trace_parent = None\n            self.client.begin_transaction(\"request\", trace_parent=trace_parent)\n            elasticapm.set_context(\n                lambda: get_data_from_request(\n                    request,\n                    capture_body=self.client.config.capture_body in (\"transactions\", \"all\"),\n                    capture_headers=self.client.config.capture_headers,\n                ),\n                \"request\",\n            )\n            rule = request.url_rule.rule if request.url_rule is not None else \"\"\n            rule = build_name_with_http_method_prefix(rule, request)\n            elasticapm.set_transaction_name(rule, override=False)\n\n    def request_finished(self, app, response):\n        if not self.app.debug or self.client.config.debug:\n            elasticapm.set_context(\n                lambda: get_data_from_response(response, capture_headers=self.client.config.capture_headers), \"response\"\n            )\n            if response.status_code:\n                result = \"HTTP {}xx\".format(response.status_code // 100)\n            else:\n                result = response.status\n            elasticapm.set_transaction_result(result, override=False)\n            # Instead of calling end_transaction here, we defer the call until the response is closed.\n            # This ensures that we capture things that happen until the WSGI server closes the response.\n            response.call_on_close(self.client.end_transaction)\n\n    def capture_exception(self, *args, **kwargs):\n        assert self.client, \"capture_exception called before application configured\"\n        return self.client.capture_exception(*args, **kwargs)\n\n    def capture_message(self, *args, **kwargs):\n        assert self.client, \"capture_message called before application configured\"\n        return self.client.capture_message(*args, **kwargs)\n", "path": "elasticapm/contrib/flask/__init__.py"}]}