We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
AttributeError: 'NoneType' object has no attribute 'config' in Flask Agent
**Describe the bug**: By using the flask agent and opting to [build the application on the fly](https://www.elastic.co/guide/en/apm/agent/python/current/flask-support.html#flask-building-applications-on-the-fly) an error occurs  **AttributeError: 'NoneType' object has no attribute 'config'**.

**To Reproduce**

1. run the following
```python
from elasticapm.contrib.flask import ElasticAPM
from flask import Flask
apm = ElasticAPM()

app = Flask(__name__)
apm.init_app(app)
```

**Environment (please complete the following information)**
- OS: Linux
- Python version: 3.8.5
- Framework and version: Flask 1.1.2 
- APM Server version: NA
- Agent version: NA


**Additional context**

This error does not happen with version 6.0.0. This bug seems to be incorporated into the project in the [PR-1043](https://github.com/elastic/apm-agent-python/pull/1043) with the removal from the [if statement L128 - L129](https://github.com/elastic/apm-agent-python/pull/1043/files#diff-f7d7281cc8ddf8897f2fe37e9ad1facc0aefb57bdda156a5de603db46d1d9b1eL128-L129) in the `init_app()` method that would initiate a possible NoneType client. Based on the efforts of the [PR-1043](https://github.com/elastic/apm-agent-python/pull/1043) to make the client global, it would ideally be initiated in the constructor from the class ElasticAPM. That would only apply [if there's an existent app](https://github.com/elastic/apm-agent-python/pull/1043/files#diff-f7d7281cc8ddf8897f2fe37e9ad1facc0aefb57bdda156a5de603db46d1d9b1eL103) which does not happen if the user opted to [build the application on the fly](https://www.elastic.co/guide/en/apm/agent/python/current/flask-support.html#flask-building-applications-on-the-fly).

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

{"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@@ -88,17 +88,9 @@\n         self.app = app\n         self.logging = logging\n         self.client = client or get_client()\n+        self.client_cls = client_cls\n \n         if app:\n-            if not self.client:\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-                self.client = client_cls(config, **defaults)\n-\n             self.init_app(app, **defaults)\n \n     def handle_exception(self, *args, **kwargs):\n@@ -123,6 +115,14 @@\n \n     def init_app(self, app, **defaults):\n         self.app = app\n+        if not self.client:\n+            config = self.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+            self.client = self.client_cls(config, **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 logging.NOTSET:\n", "issue": "AttributeError: 'NoneType' object has no attribute 'config' in Flask Agent\n**Describe the bug**: By using the flask agent and opting to [build the application on the fly](https://www.elastic.co/guide/en/apm/agent/python/current/flask-support.html#flask-building-applications-on-the-fly) an error occurs  **AttributeError: 'NoneType' object has no attribute 'config'**.\r\n\r\n**To Reproduce**\r\n\r\n1. run the following\r\n```python\r\nfrom elasticapm.contrib.flask import ElasticAPM\r\nfrom flask import Flask\r\napm = ElasticAPM()\r\n\r\napp = Flask(__name__)\r\napm.init_app(app)\r\n```\r\n\r\n**Environment (please complete the following information)**\r\n- OS: Linux\r\n- Python version: 3.8.5\r\n- Framework and version: Flask 1.1.2 \r\n- APM Server version: NA\r\n- Agent version: NA\r\n\r\n\r\n**Additional context**\r\n\r\nThis error does not happen with version 6.0.0. This bug seems to be incorporated into the project in the [PR-1043](https://github.com/elastic/apm-agent-python/pull/1043) with the removal from the [if statement L128 - L129](https://github.com/elastic/apm-agent-python/pull/1043/files#diff-f7d7281cc8ddf8897f2fe37e9ad1facc0aefb57bdda156a5de603db46d1d9b1eL128-L129) in the `init_app()` method that would initiate a possible NoneType client. Based on the efforts of the [PR-1043](https://github.com/elastic/apm-agent-python/pull/1043) to make the client global, it would ideally be initiated in the constructor from the class ElasticAPM. That would only apply [if there's an existent app](https://github.com/elastic/apm-agent-python/pull/1043/files#diff-f7d7281cc8ddf8897f2fe37e9ad1facc0aefb57bdda156a5de603db46d1d9b1eL103) which does not happen if the user opted to [build the application on the fly](https://www.elastic.co/guide/en/apm/agent/python/current/flask-support.html#flask-building-applications-on-the-fly).\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\n\nimport flask\nfrom flask import request, signals\n\nimport elasticapm\nimport elasticapm.instrumentation.control\nfrom elasticapm import get_client\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\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 = client or get_client()\n\n        if app:\n            if not self.client:\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                self.client = client_cls(config, **defaults)\n\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={\"request\": get_data_from_request(request, self.client.config, constants.ERROR)},\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\n        # 0 is a valid log level (NOTSET), so we need to check explicitly for it\n        if self.logging or self.logging is logging.NOTSET:\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 and self.client.config.enabled:\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) and not self.client.should_ignore_url(request.path):\n            trace_parent = TraceParent.from_headers(request.headers)\n            self.client.begin_transaction(\"request\", trace_parent=trace_parent)\n            elasticapm.set_context(\n                lambda: get_data_from_request(request, self.client.config, constants.TRANSACTION), \"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, self.client.config, constants.TRANSACTION), \"response\"\n            )\n            if response.status_code:\n                result = \"HTTP {}xx\".format(response.status_code // 100)\n                elasticapm.set_transaction_outcome(http_status_code=response.status_code, override=False)\n            else:\n                result = response.status\n                elasticapm.set_transaction_outcome(http_status_code=response.status, 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 logging\n\nimport flask\nfrom flask import request, signals\n\nimport elasticapm\nimport elasticapm.instrumentation.control\nfrom elasticapm import get_client\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\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 = client or get_client()\n        self.client_cls = client_cls\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={\"request\": get_data_from_request(request, self.client.config, constants.ERROR)},\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            config = self.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            self.client = self.client_cls(config, **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 logging.NOTSET:\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 and self.client.config.enabled:\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) and not self.client.should_ignore_url(request.path):\n            trace_parent = TraceParent.from_headers(request.headers)\n            self.client.begin_transaction(\"request\", trace_parent=trace_parent)\n            elasticapm.set_context(\n                lambda: get_data_from_request(request, self.client.config, constants.TRANSACTION), \"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, self.client.config, constants.TRANSACTION), \"response\"\n            )\n            if response.status_code:\n                result = \"HTTP {}xx\".format(response.status_code // 100)\n                elasticapm.set_transaction_outcome(http_status_code=response.status_code, override=False)\n            else:\n                result = response.status\n                elasticapm.set_transaction_outcome(http_status_code=response.status, 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"}]}