We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Flask: Capture request/response headers as span attributes
part of #906 


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py`
Content:
```
1 # Copyright The OpenTelemetry Authors
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 # Note: This package is not named "flask" because of
16 # https://github.com/PyCQA/pylint/issues/2648
17 
18 """
19 This library builds on the OpenTelemetry WSGI middleware to track web requests
20 in Flask applications. In addition to opentelemetry-util-http, it
21 supports Flask-specific features such as:
22 
23 * The Flask url rule pattern is used as the Span name.
24 * The ``http.route`` Span attribute is set so that one can see which URL rule
25   matched a request.
26 
27 Usage
28 -----
29 
30 .. code-block:: python
31 
32     from flask import Flask
33     from opentelemetry.instrumentation.flask import FlaskInstrumentor
34 
35     app = Flask(__name__)
36 
37     FlaskInstrumentor().instrument_app(app)
38 
39     @app.route("/")
40     def hello():
41         return "Hello!"
42 
43     if __name__ == "__main__":
44         app.run(debug=True)
45 
46 Configuration
47 -------------
48 
49 Exclude lists
50 *************
51 To exclude certain URLs from being tracked, set the environment variable ``OTEL_PYTHON_FLASK_EXCLUDED_URLS``
52 (or ``OTEL_PYTHON_EXCLUDED_URLS`` as fallback) with comma delimited regexes representing which URLs to exclude.
53 
54 For example,
55 
56 ::
57 
58     export OTEL_PYTHON_FLASK_EXCLUDED_URLS="client/.*/info,healthcheck"
59 
60 will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``.
61 
62 You can also pass the comma delimited regexes to the ``instrument_app`` method directly:
63 
64 .. code-block:: python
65 
66     FlaskInstrumentor().instrument_app(app, excluded_urls="client/.*/info,healthcheck")
67 
68 Request/Response hooks
69 **********************
70 
71 Utilize request/reponse hooks to execute custom logic to be performed before/after performing a request. Environ is an instance of WSGIEnvironment (flask.request.environ).
72 Response_headers is a list of key-value (tuples) representing the response headers returned from the response.
73 
74 .. code-block:: python
75 
76     def request_hook(span: Span, environ: WSGIEnvironment):
77         if span and span.is_recording():
78             span.set_attribute("custom_user_attribute_from_request_hook", "some-value")
79 
80     def response_hook(span: Span, status: str, response_headers: List):
81         if span and span.is_recording():
82             span.set_attribute("custom_user_attribute_from_response_hook", "some-value")
83 
84     FlaskInstrumentation().instrument(request_hook=request_hook, response_hook=response_hook)
85 
86 Flask Request object reference: https://flask.palletsprojects.com/en/2.0.x/api/#flask.Request
87 
88 API
89 ---
90 """
91 
92 from logging import getLogger
93 from typing import Collection
94 
95 import flask
96 
97 import opentelemetry.instrumentation.wsgi as otel_wsgi
98 from opentelemetry import context, trace
99 from opentelemetry.instrumentation.flask.package import _instruments
100 from opentelemetry.instrumentation.flask.version import __version__
101 from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
102 from opentelemetry.instrumentation.propagators import (
103     get_global_response_propagator,
104 )
105 from opentelemetry.instrumentation.utils import _start_internal_or_server_span
106 from opentelemetry.semconv.trace import SpanAttributes
107 from opentelemetry.util._time import _time_ns
108 from opentelemetry.util.http import get_excluded_urls, parse_excluded_urls
109 
110 _logger = getLogger(__name__)
111 
112 _ENVIRON_STARTTIME_KEY = "opentelemetry-flask.starttime_key"
113 _ENVIRON_SPAN_KEY = "opentelemetry-flask.span_key"
114 _ENVIRON_ACTIVATION_KEY = "opentelemetry-flask.activation_key"
115 _ENVIRON_TOKEN = "opentelemetry-flask.token"
116 
117 
118 _excluded_urls_from_env = get_excluded_urls("FLASK")
119 
120 
121 def get_default_span_name():
122     span_name = ""
123     try:
124         span_name = flask.request.url_rule.rule
125     except AttributeError:
126         span_name = otel_wsgi.get_default_span_name(flask.request.environ)
127     return span_name
128 
129 
130 def _rewrapped_app(wsgi_app, response_hook=None, excluded_urls=None):
131     def _wrapped_app(wrapped_app_environ, start_response):
132         # We want to measure the time for route matching, etc.
133         # In theory, we could start the span here and use
134         # update_name later but that API is "highly discouraged" so
135         # we better avoid it.
136         wrapped_app_environ[_ENVIRON_STARTTIME_KEY] = _time_ns()
137 
138         def _start_response(status, response_headers, *args, **kwargs):
139             if flask.request and (
140                 excluded_urls is None
141                 or not excluded_urls.url_disabled(flask.request.url)
142             ):
143                 span = flask.request.environ.get(_ENVIRON_SPAN_KEY)
144 
145                 propagator = get_global_response_propagator()
146                 if propagator:
147                     propagator.inject(
148                         response_headers,
149                         setter=otel_wsgi.default_response_propagation_setter,
150                     )
151 
152                 if span:
153                     otel_wsgi.add_response_attributes(
154                         span, status, response_headers
155                     )
156                 else:
157                     _logger.warning(
158                         "Flask environ's OpenTelemetry span "
159                         "missing at _start_response(%s)",
160                         status,
161                     )
162                 if response_hook is not None:
163                     response_hook(span, status, response_headers)
164             return start_response(status, response_headers, *args, **kwargs)
165 
166         return wsgi_app(wrapped_app_environ, _start_response)
167 
168     return _wrapped_app
169 
170 
171 def _wrapped_before_request(
172     request_hook=None, tracer=None, excluded_urls=None
173 ):
174     def _before_request():
175         if excluded_urls and excluded_urls.url_disabled(flask.request.url):
176             return
177         flask_request_environ = flask.request.environ
178         span_name = get_default_span_name()
179 
180         span, token = _start_internal_or_server_span(
181             tracer=tracer,
182             span_name=span_name,
183             start_time=flask_request_environ.get(_ENVIRON_STARTTIME_KEY),
184             context_carrier=flask_request_environ,
185             context_getter=otel_wsgi.wsgi_getter,
186         )
187 
188         if request_hook:
189             request_hook(span, flask_request_environ)
190 
191         if span.is_recording():
192             attributes = otel_wsgi.collect_request_attributes(
193                 flask_request_environ
194             )
195             if flask.request.url_rule:
196                 # For 404 that result from no route found, etc, we
197                 # don't have a url_rule.
198                 attributes[
199                     SpanAttributes.HTTP_ROUTE
200                 ] = flask.request.url_rule.rule
201             for key, value in attributes.items():
202                 span.set_attribute(key, value)
203 
204         activation = trace.use_span(span, end_on_exit=True)
205         activation.__enter__()  # pylint: disable=E1101
206         flask_request_environ[_ENVIRON_ACTIVATION_KEY] = activation
207         flask_request_environ[_ENVIRON_SPAN_KEY] = span
208         flask_request_environ[_ENVIRON_TOKEN] = token
209 
210     return _before_request
211 
212 
213 def _wrapped_teardown_request(excluded_urls=None):
214     def _teardown_request(exc):
215         # pylint: disable=E1101
216         if excluded_urls and excluded_urls.url_disabled(flask.request.url):
217             return
218 
219         activation = flask.request.environ.get(_ENVIRON_ACTIVATION_KEY)
220         if not activation:
221             # This request didn't start a span, maybe because it was created in
222             # a way that doesn't run `before_request`, like when it is created
223             # with `app.test_request_context`.
224             return
225 
226         if exc is None:
227             activation.__exit__(None, None, None)
228         else:
229             activation.__exit__(
230                 type(exc), exc, getattr(exc, "__traceback__", None)
231             )
232 
233         if flask.request.environ.get(_ENVIRON_TOKEN, None):
234             context.detach(flask.request.environ.get(_ENVIRON_TOKEN))
235 
236     return _teardown_request
237 
238 
239 class _InstrumentedFlask(flask.Flask):
240 
241     _excluded_urls = None
242     _tracer_provider = None
243     _request_hook = None
244     _response_hook = None
245 
246     def __init__(self, *args, **kwargs):
247         super().__init__(*args, **kwargs)
248 
249         self._original_wsgi_app = self.wsgi_app
250         self._is_instrumented_by_opentelemetry = True
251 
252         self.wsgi_app = _rewrapped_app(
253             self.wsgi_app,
254             _InstrumentedFlask._response_hook,
255             excluded_urls=_InstrumentedFlask._excluded_urls,
256         )
257 
258         tracer = trace.get_tracer(
259             __name__, __version__, _InstrumentedFlask._tracer_provider
260         )
261 
262         _before_request = _wrapped_before_request(
263             _InstrumentedFlask._request_hook,
264             tracer,
265             excluded_urls=_InstrumentedFlask._excluded_urls,
266         )
267         self._before_request = _before_request
268         self.before_request(_before_request)
269 
270         _teardown_request = _wrapped_teardown_request(
271             excluded_urls=_InstrumentedFlask._excluded_urls,
272         )
273         self.teardown_request(_teardown_request)
274 
275 
276 class FlaskInstrumentor(BaseInstrumentor):
277     # pylint: disable=protected-access,attribute-defined-outside-init
278     """An instrumentor for flask.Flask
279 
280     See `BaseInstrumentor`
281     """
282 
283     def instrumentation_dependencies(self) -> Collection[str]:
284         return _instruments
285 
286     def _instrument(self, **kwargs):
287         self._original_flask = flask.Flask
288         request_hook = kwargs.get("request_hook")
289         response_hook = kwargs.get("response_hook")
290         if callable(request_hook):
291             _InstrumentedFlask._request_hook = request_hook
292         if callable(response_hook):
293             _InstrumentedFlask._response_hook = response_hook
294         tracer_provider = kwargs.get("tracer_provider")
295         _InstrumentedFlask._tracer_provider = tracer_provider
296         excluded_urls = kwargs.get("excluded_urls")
297         _InstrumentedFlask._excluded_urls = (
298             _excluded_urls_from_env
299             if excluded_urls is None
300             else parse_excluded_urls(excluded_urls)
301         )
302         flask.Flask = _InstrumentedFlask
303 
304     def _uninstrument(self, **kwargs):
305         flask.Flask = self._original_flask
306 
307     @staticmethod
308     def instrument_app(
309         app,
310         request_hook=None,
311         response_hook=None,
312         tracer_provider=None,
313         excluded_urls=None,
314     ):
315         if not hasattr(app, "_is_instrumented_by_opentelemetry"):
316             app._is_instrumented_by_opentelemetry = False
317 
318         if not app._is_instrumented_by_opentelemetry:
319             excluded_urls = (
320                 parse_excluded_urls(excluded_urls)
321                 if excluded_urls is not None
322                 else _excluded_urls_from_env
323             )
324             app._original_wsgi_app = app.wsgi_app
325             app.wsgi_app = _rewrapped_app(
326                 app.wsgi_app, response_hook, excluded_urls=excluded_urls
327             )
328 
329             tracer = trace.get_tracer(__name__, __version__, tracer_provider)
330 
331             _before_request = _wrapped_before_request(
332                 request_hook,
333                 tracer,
334                 excluded_urls=excluded_urls,
335             )
336             app._before_request = _before_request
337             app.before_request(_before_request)
338 
339             _teardown_request = _wrapped_teardown_request(
340                 excluded_urls=excluded_urls,
341             )
342             app._teardown_request = _teardown_request
343             app.teardown_request(_teardown_request)
344             app._is_instrumented_by_opentelemetry = True
345         else:
346             _logger.warning(
347                 "Attempting to instrument Flask app while already instrumented"
348             )
349 
350     @staticmethod
351     def uninstrument_app(app):
352         if hasattr(app, "_original_wsgi_app"):
353             app.wsgi_app = app._original_wsgi_app
354 
355             # FIXME add support for other Flask blueprints that are not None
356             app.before_request_funcs[None].remove(app._before_request)
357             app.teardown_request_funcs[None].remove(app._teardown_request)
358             del app._original_wsgi_app
359             app._is_instrumented_by_opentelemetry = False
360         else:
361             _logger.warning(
362                 "Attempting to uninstrument Flask "
363                 "app while already uninstrumented"
364             )
365
```
--- 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/instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py b/instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py
--- a/instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py
+++ b/instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py
@@ -153,6 +153,10 @@
                     otel_wsgi.add_response_attributes(
                         span, status, response_headers
                     )
+                    if span.kind == trace.SpanKind.SERVER:
+                        otel_wsgi.add_custom_response_headers(
+                            span, response_headers
+                        )
                 else:
                     _logger.warning(
                         "Flask environ's OpenTelemetry span "
@@ -200,6 +204,10 @@
                 ] = flask.request.url_rule.rule
             for key, value in attributes.items():
                 span.set_attribute(key, value)
+            if span.kind == trace.SpanKind.SERVER:
+                otel_wsgi.add_custom_request_headers(
+                    span, flask_request_environ
+                )
 
         activation = trace.use_span(span, end_on_exit=True)
         activation.__enter__()  # pylint: disable=E1101

{"golden_diff": "diff --git a/instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py b/instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py\n--- a/instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py\n+++ b/instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py\n@@ -153,6 +153,10 @@\n                     otel_wsgi.add_response_attributes(\n                         span, status, response_headers\n                     )\n+                    if span.kind == trace.SpanKind.SERVER:\n+                        otel_wsgi.add_custom_response_headers(\n+                            span, response_headers\n+                        )\n                 else:\n                     _logger.warning(\n                         \"Flask environ's OpenTelemetry span \"\n@@ -200,6 +204,10 @@\n                 ] = flask.request.url_rule.rule\n             for key, value in attributes.items():\n                 span.set_attribute(key, value)\n+            if span.kind == trace.SpanKind.SERVER:\n+                otel_wsgi.add_custom_request_headers(\n+                    span, flask_request_environ\n+                )\n \n         activation = trace.use_span(span, end_on_exit=True)\n         activation.__enter__()  # pylint: disable=E1101\n", "issue": "Flask: Capture request/response headers as span attributes\npart of #906 \r\n\n", "before_files": [{"content": "# Copyright The OpenTelemetry Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Note: This package is not named \"flask\" because of\n# https://github.com/PyCQA/pylint/issues/2648\n\n\"\"\"\nThis library builds on the OpenTelemetry WSGI middleware to track web requests\nin Flask applications. In addition to opentelemetry-util-http, it\nsupports Flask-specific features such as:\n\n* The Flask url rule pattern is used as the Span name.\n* The ``http.route`` Span attribute is set so that one can see which URL rule\n  matched a request.\n\nUsage\n-----\n\n.. code-block:: python\n\n    from flask import Flask\n    from opentelemetry.instrumentation.flask import FlaskInstrumentor\n\n    app = Flask(__name__)\n\n    FlaskInstrumentor().instrument_app(app)\n\n    @app.route(\"/\")\n    def hello():\n        return \"Hello!\"\n\n    if __name__ == \"__main__\":\n        app.run(debug=True)\n\nConfiguration\n-------------\n\nExclude lists\n*************\nTo exclude certain URLs from being tracked, set the environment variable ``OTEL_PYTHON_FLASK_EXCLUDED_URLS``\n(or ``OTEL_PYTHON_EXCLUDED_URLS`` as fallback) with comma delimited regexes representing which URLs to exclude.\n\nFor example,\n\n::\n\n    export OTEL_PYTHON_FLASK_EXCLUDED_URLS=\"client/.*/info,healthcheck\"\n\nwill exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``.\n\nYou can also pass the comma delimited regexes to the ``instrument_app`` method directly:\n\n.. code-block:: python\n\n    FlaskInstrumentor().instrument_app(app, excluded_urls=\"client/.*/info,healthcheck\")\n\nRequest/Response hooks\n**********************\n\nUtilize request/reponse hooks to execute custom logic to be performed before/after performing a request. Environ is an instance of WSGIEnvironment (flask.request.environ).\nResponse_headers is a list of key-value (tuples) representing the response headers returned from the response.\n\n.. code-block:: python\n\n    def request_hook(span: Span, environ: WSGIEnvironment):\n        if span and span.is_recording():\n            span.set_attribute(\"custom_user_attribute_from_request_hook\", \"some-value\")\n\n    def response_hook(span: Span, status: str, response_headers: List):\n        if span and span.is_recording():\n            span.set_attribute(\"custom_user_attribute_from_response_hook\", \"some-value\")\n\n    FlaskInstrumentation().instrument(request_hook=request_hook, response_hook=response_hook)\n\nFlask Request object reference: https://flask.palletsprojects.com/en/2.0.x/api/#flask.Request\n\nAPI\n---\n\"\"\"\n\nfrom logging import getLogger\nfrom typing import Collection\n\nimport flask\n\nimport opentelemetry.instrumentation.wsgi as otel_wsgi\nfrom opentelemetry import context, trace\nfrom opentelemetry.instrumentation.flask.package import _instruments\nfrom opentelemetry.instrumentation.flask.version import __version__\nfrom opentelemetry.instrumentation.instrumentor import BaseInstrumentor\nfrom opentelemetry.instrumentation.propagators import (\n    get_global_response_propagator,\n)\nfrom opentelemetry.instrumentation.utils import _start_internal_or_server_span\nfrom opentelemetry.semconv.trace import SpanAttributes\nfrom opentelemetry.util._time import _time_ns\nfrom opentelemetry.util.http import get_excluded_urls, parse_excluded_urls\n\n_logger = getLogger(__name__)\n\n_ENVIRON_STARTTIME_KEY = \"opentelemetry-flask.starttime_key\"\n_ENVIRON_SPAN_KEY = \"opentelemetry-flask.span_key\"\n_ENVIRON_ACTIVATION_KEY = \"opentelemetry-flask.activation_key\"\n_ENVIRON_TOKEN = \"opentelemetry-flask.token\"\n\n\n_excluded_urls_from_env = get_excluded_urls(\"FLASK\")\n\n\ndef get_default_span_name():\n    span_name = \"\"\n    try:\n        span_name = flask.request.url_rule.rule\n    except AttributeError:\n        span_name = otel_wsgi.get_default_span_name(flask.request.environ)\n    return span_name\n\n\ndef _rewrapped_app(wsgi_app, response_hook=None, excluded_urls=None):\n    def _wrapped_app(wrapped_app_environ, start_response):\n        # We want to measure the time for route matching, etc.\n        # In theory, we could start the span here and use\n        # update_name later but that API is \"highly discouraged\" so\n        # we better avoid it.\n        wrapped_app_environ[_ENVIRON_STARTTIME_KEY] = _time_ns()\n\n        def _start_response(status, response_headers, *args, **kwargs):\n            if flask.request and (\n                excluded_urls is None\n                or not excluded_urls.url_disabled(flask.request.url)\n            ):\n                span = flask.request.environ.get(_ENVIRON_SPAN_KEY)\n\n                propagator = get_global_response_propagator()\n                if propagator:\n                    propagator.inject(\n                        response_headers,\n                        setter=otel_wsgi.default_response_propagation_setter,\n                    )\n\n                if span:\n                    otel_wsgi.add_response_attributes(\n                        span, status, response_headers\n                    )\n                else:\n                    _logger.warning(\n                        \"Flask environ's OpenTelemetry span \"\n                        \"missing at _start_response(%s)\",\n                        status,\n                    )\n                if response_hook is not None:\n                    response_hook(span, status, response_headers)\n            return start_response(status, response_headers, *args, **kwargs)\n\n        return wsgi_app(wrapped_app_environ, _start_response)\n\n    return _wrapped_app\n\n\ndef _wrapped_before_request(\n    request_hook=None, tracer=None, excluded_urls=None\n):\n    def _before_request():\n        if excluded_urls and excluded_urls.url_disabled(flask.request.url):\n            return\n        flask_request_environ = flask.request.environ\n        span_name = get_default_span_name()\n\n        span, token = _start_internal_or_server_span(\n            tracer=tracer,\n            span_name=span_name,\n            start_time=flask_request_environ.get(_ENVIRON_STARTTIME_KEY),\n            context_carrier=flask_request_environ,\n            context_getter=otel_wsgi.wsgi_getter,\n        )\n\n        if request_hook:\n            request_hook(span, flask_request_environ)\n\n        if span.is_recording():\n            attributes = otel_wsgi.collect_request_attributes(\n                flask_request_environ\n            )\n            if flask.request.url_rule:\n                # For 404 that result from no route found, etc, we\n                # don't have a url_rule.\n                attributes[\n                    SpanAttributes.HTTP_ROUTE\n                ] = flask.request.url_rule.rule\n            for key, value in attributes.items():\n                span.set_attribute(key, value)\n\n        activation = trace.use_span(span, end_on_exit=True)\n        activation.__enter__()  # pylint: disable=E1101\n        flask_request_environ[_ENVIRON_ACTIVATION_KEY] = activation\n        flask_request_environ[_ENVIRON_SPAN_KEY] = span\n        flask_request_environ[_ENVIRON_TOKEN] = token\n\n    return _before_request\n\n\ndef _wrapped_teardown_request(excluded_urls=None):\n    def _teardown_request(exc):\n        # pylint: disable=E1101\n        if excluded_urls and excluded_urls.url_disabled(flask.request.url):\n            return\n\n        activation = flask.request.environ.get(_ENVIRON_ACTIVATION_KEY)\n        if not activation:\n            # This request didn't start a span, maybe because it was created in\n            # a way that doesn't run `before_request`, like when it is created\n            # with `app.test_request_context`.\n            return\n\n        if exc is None:\n            activation.__exit__(None, None, None)\n        else:\n            activation.__exit__(\n                type(exc), exc, getattr(exc, \"__traceback__\", None)\n            )\n\n        if flask.request.environ.get(_ENVIRON_TOKEN, None):\n            context.detach(flask.request.environ.get(_ENVIRON_TOKEN))\n\n    return _teardown_request\n\n\nclass _InstrumentedFlask(flask.Flask):\n\n    _excluded_urls = None\n    _tracer_provider = None\n    _request_hook = None\n    _response_hook = None\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        self._original_wsgi_app = self.wsgi_app\n        self._is_instrumented_by_opentelemetry = True\n\n        self.wsgi_app = _rewrapped_app(\n            self.wsgi_app,\n            _InstrumentedFlask._response_hook,\n            excluded_urls=_InstrumentedFlask._excluded_urls,\n        )\n\n        tracer = trace.get_tracer(\n            __name__, __version__, _InstrumentedFlask._tracer_provider\n        )\n\n        _before_request = _wrapped_before_request(\n            _InstrumentedFlask._request_hook,\n            tracer,\n            excluded_urls=_InstrumentedFlask._excluded_urls,\n        )\n        self._before_request = _before_request\n        self.before_request(_before_request)\n\n        _teardown_request = _wrapped_teardown_request(\n            excluded_urls=_InstrumentedFlask._excluded_urls,\n        )\n        self.teardown_request(_teardown_request)\n\n\nclass FlaskInstrumentor(BaseInstrumentor):\n    # pylint: disable=protected-access,attribute-defined-outside-init\n    \"\"\"An instrumentor for flask.Flask\n\n    See `BaseInstrumentor`\n    \"\"\"\n\n    def instrumentation_dependencies(self) -> Collection[str]:\n        return _instruments\n\n    def _instrument(self, **kwargs):\n        self._original_flask = flask.Flask\n        request_hook = kwargs.get(\"request_hook\")\n        response_hook = kwargs.get(\"response_hook\")\n        if callable(request_hook):\n            _InstrumentedFlask._request_hook = request_hook\n        if callable(response_hook):\n            _InstrumentedFlask._response_hook = response_hook\n        tracer_provider = kwargs.get(\"tracer_provider\")\n        _InstrumentedFlask._tracer_provider = tracer_provider\n        excluded_urls = kwargs.get(\"excluded_urls\")\n        _InstrumentedFlask._excluded_urls = (\n            _excluded_urls_from_env\n            if excluded_urls is None\n            else parse_excluded_urls(excluded_urls)\n        )\n        flask.Flask = _InstrumentedFlask\n\n    def _uninstrument(self, **kwargs):\n        flask.Flask = self._original_flask\n\n    @staticmethod\n    def instrument_app(\n        app,\n        request_hook=None,\n        response_hook=None,\n        tracer_provider=None,\n        excluded_urls=None,\n    ):\n        if not hasattr(app, \"_is_instrumented_by_opentelemetry\"):\n            app._is_instrumented_by_opentelemetry = False\n\n        if not app._is_instrumented_by_opentelemetry:\n            excluded_urls = (\n                parse_excluded_urls(excluded_urls)\n                if excluded_urls is not None\n                else _excluded_urls_from_env\n            )\n            app._original_wsgi_app = app.wsgi_app\n            app.wsgi_app = _rewrapped_app(\n                app.wsgi_app, response_hook, excluded_urls=excluded_urls\n            )\n\n            tracer = trace.get_tracer(__name__, __version__, tracer_provider)\n\n            _before_request = _wrapped_before_request(\n                request_hook,\n                tracer,\n                excluded_urls=excluded_urls,\n            )\n            app._before_request = _before_request\n            app.before_request(_before_request)\n\n            _teardown_request = _wrapped_teardown_request(\n                excluded_urls=excluded_urls,\n            )\n            app._teardown_request = _teardown_request\n            app.teardown_request(_teardown_request)\n            app._is_instrumented_by_opentelemetry = True\n        else:\n            _logger.warning(\n                \"Attempting to instrument Flask app while already instrumented\"\n            )\n\n    @staticmethod\n    def uninstrument_app(app):\n        if hasattr(app, \"_original_wsgi_app\"):\n            app.wsgi_app = app._original_wsgi_app\n\n            # FIXME add support for other Flask blueprints that are not None\n            app.before_request_funcs[None].remove(app._before_request)\n            app.teardown_request_funcs[None].remove(app._teardown_request)\n            del app._original_wsgi_app\n            app._is_instrumented_by_opentelemetry = False\n        else:\n            _logger.warning(\n                \"Attempting to uninstrument Flask \"\n                \"app while already uninstrumented\"\n            )\n", "path": "instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py"}], "after_files": [{"content": "# Copyright The OpenTelemetry Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Note: This package is not named \"flask\" because of\n# https://github.com/PyCQA/pylint/issues/2648\n\n\"\"\"\nThis library builds on the OpenTelemetry WSGI middleware to track web requests\nin Flask applications. In addition to opentelemetry-util-http, it\nsupports Flask-specific features such as:\n\n* The Flask url rule pattern is used as the Span name.\n* The ``http.route`` Span attribute is set so that one can see which URL rule\n  matched a request.\n\nUsage\n-----\n\n.. code-block:: python\n\n    from flask import Flask\n    from opentelemetry.instrumentation.flask import FlaskInstrumentor\n\n    app = Flask(__name__)\n\n    FlaskInstrumentor().instrument_app(app)\n\n    @app.route(\"/\")\n    def hello():\n        return \"Hello!\"\n\n    if __name__ == \"__main__\":\n        app.run(debug=True)\n\nConfiguration\n-------------\n\nExclude lists\n*************\nTo exclude certain URLs from being tracked, set the environment variable ``OTEL_PYTHON_FLASK_EXCLUDED_URLS``\n(or ``OTEL_PYTHON_EXCLUDED_URLS`` as fallback) with comma delimited regexes representing which URLs to exclude.\n\nFor example,\n\n::\n\n    export OTEL_PYTHON_FLASK_EXCLUDED_URLS=\"client/.*/info,healthcheck\"\n\nwill exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``.\n\nYou can also pass the comma delimited regexes to the ``instrument_app`` method directly:\n\n.. code-block:: python\n\n    FlaskInstrumentor().instrument_app(app, excluded_urls=\"client/.*/info,healthcheck\")\n\nRequest/Response hooks\n**********************\n\nUtilize request/reponse hooks to execute custom logic to be performed before/after performing a request. Environ is an instance of WSGIEnvironment (flask.request.environ).\nResponse_headers is a list of key-value (tuples) representing the response headers returned from the response.\n\n.. code-block:: python\n\n    def request_hook(span: Span, environ: WSGIEnvironment):\n        if span and span.is_recording():\n            span.set_attribute(\"custom_user_attribute_from_request_hook\", \"some-value\")\n\n    def response_hook(span: Span, status: str, response_headers: List):\n        if span and span.is_recording():\n            span.set_attribute(\"custom_user_attribute_from_response_hook\", \"some-value\")\n\n    FlaskInstrumentation().instrument(request_hook=request_hook, response_hook=response_hook)\n\nFlask Request object reference: https://flask.palletsprojects.com/en/2.0.x/api/#flask.Request\n\nAPI\n---\n\"\"\"\n\nfrom logging import getLogger\nfrom typing import Collection\n\nimport flask\n\nimport opentelemetry.instrumentation.wsgi as otel_wsgi\nfrom opentelemetry import context, trace\nfrom opentelemetry.instrumentation.flask.package import _instruments\nfrom opentelemetry.instrumentation.flask.version import __version__\nfrom opentelemetry.instrumentation.instrumentor import BaseInstrumentor\nfrom opentelemetry.instrumentation.propagators import (\n    get_global_response_propagator,\n)\nfrom opentelemetry.instrumentation.utils import _start_internal_or_server_span\nfrom opentelemetry.semconv.trace import SpanAttributes\nfrom opentelemetry.util._time import _time_ns\nfrom opentelemetry.util.http import get_excluded_urls, parse_excluded_urls\n\n_logger = getLogger(__name__)\n\n_ENVIRON_STARTTIME_KEY = \"opentelemetry-flask.starttime_key\"\n_ENVIRON_SPAN_KEY = \"opentelemetry-flask.span_key\"\n_ENVIRON_ACTIVATION_KEY = \"opentelemetry-flask.activation_key\"\n_ENVIRON_TOKEN = \"opentelemetry-flask.token\"\n\n\n_excluded_urls_from_env = get_excluded_urls(\"FLASK\")\n\n\ndef get_default_span_name():\n    span_name = \"\"\n    try:\n        span_name = flask.request.url_rule.rule\n    except AttributeError:\n        span_name = otel_wsgi.get_default_span_name(flask.request.environ)\n    return span_name\n\n\ndef _rewrapped_app(wsgi_app, response_hook=None, excluded_urls=None):\n    def _wrapped_app(wrapped_app_environ, start_response):\n        # We want to measure the time for route matching, etc.\n        # In theory, we could start the span here and use\n        # update_name later but that API is \"highly discouraged\" so\n        # we better avoid it.\n        wrapped_app_environ[_ENVIRON_STARTTIME_KEY] = _time_ns()\n\n        def _start_response(status, response_headers, *args, **kwargs):\n            if flask.request and (\n                excluded_urls is None\n                or not excluded_urls.url_disabled(flask.request.url)\n            ):\n                span = flask.request.environ.get(_ENVIRON_SPAN_KEY)\n\n                propagator = get_global_response_propagator()\n                if propagator:\n                    propagator.inject(\n                        response_headers,\n                        setter=otel_wsgi.default_response_propagation_setter,\n                    )\n\n                if span:\n                    otel_wsgi.add_response_attributes(\n                        span, status, response_headers\n                    )\n                    if span.kind == trace.SpanKind.SERVER:\n                        otel_wsgi.add_custom_response_headers(\n                            span, response_headers\n                        )\n                else:\n                    _logger.warning(\n                        \"Flask environ's OpenTelemetry span \"\n                        \"missing at _start_response(%s)\",\n                        status,\n                    )\n                if response_hook is not None:\n                    response_hook(span, status, response_headers)\n            return start_response(status, response_headers, *args, **kwargs)\n\n        return wsgi_app(wrapped_app_environ, _start_response)\n\n    return _wrapped_app\n\n\ndef _wrapped_before_request(\n    request_hook=None, tracer=None, excluded_urls=None\n):\n    def _before_request():\n        if excluded_urls and excluded_urls.url_disabled(flask.request.url):\n            return\n        flask_request_environ = flask.request.environ\n        span_name = get_default_span_name()\n\n        span, token = _start_internal_or_server_span(\n            tracer=tracer,\n            span_name=span_name,\n            start_time=flask_request_environ.get(_ENVIRON_STARTTIME_KEY),\n            context_carrier=flask_request_environ,\n            context_getter=otel_wsgi.wsgi_getter,\n        )\n\n        if request_hook:\n            request_hook(span, flask_request_environ)\n\n        if span.is_recording():\n            attributes = otel_wsgi.collect_request_attributes(\n                flask_request_environ\n            )\n            if flask.request.url_rule:\n                # For 404 that result from no route found, etc, we\n                # don't have a url_rule.\n                attributes[\n                    SpanAttributes.HTTP_ROUTE\n                ] = flask.request.url_rule.rule\n            for key, value in attributes.items():\n                span.set_attribute(key, value)\n            if span.kind == trace.SpanKind.SERVER:\n                otel_wsgi.add_custom_request_headers(\n                    span, flask_request_environ\n                )\n\n        activation = trace.use_span(span, end_on_exit=True)\n        activation.__enter__()  # pylint: disable=E1101\n        flask_request_environ[_ENVIRON_ACTIVATION_KEY] = activation\n        flask_request_environ[_ENVIRON_SPAN_KEY] = span\n        flask_request_environ[_ENVIRON_TOKEN] = token\n\n    return _before_request\n\n\ndef _wrapped_teardown_request(excluded_urls=None):\n    def _teardown_request(exc):\n        # pylint: disable=E1101\n        if excluded_urls and excluded_urls.url_disabled(flask.request.url):\n            return\n\n        activation = flask.request.environ.get(_ENVIRON_ACTIVATION_KEY)\n        if not activation:\n            # This request didn't start a span, maybe because it was created in\n            # a way that doesn't run `before_request`, like when it is created\n            # with `app.test_request_context`.\n            return\n\n        if exc is None:\n            activation.__exit__(None, None, None)\n        else:\n            activation.__exit__(\n                type(exc), exc, getattr(exc, \"__traceback__\", None)\n            )\n\n        if flask.request.environ.get(_ENVIRON_TOKEN, None):\n            context.detach(flask.request.environ.get(_ENVIRON_TOKEN))\n\n    return _teardown_request\n\n\nclass _InstrumentedFlask(flask.Flask):\n\n    _excluded_urls = None\n    _tracer_provider = None\n    _request_hook = None\n    _response_hook = None\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        self._original_wsgi_app = self.wsgi_app\n        self._is_instrumented_by_opentelemetry = True\n\n        self.wsgi_app = _rewrapped_app(\n            self.wsgi_app,\n            _InstrumentedFlask._response_hook,\n            excluded_urls=_InstrumentedFlask._excluded_urls,\n        )\n\n        tracer = trace.get_tracer(\n            __name__, __version__, _InstrumentedFlask._tracer_provider\n        )\n\n        _before_request = _wrapped_before_request(\n            _InstrumentedFlask._request_hook,\n            tracer,\n            excluded_urls=_InstrumentedFlask._excluded_urls,\n        )\n        self._before_request = _before_request\n        self.before_request(_before_request)\n\n        _teardown_request = _wrapped_teardown_request(\n            excluded_urls=_InstrumentedFlask._excluded_urls,\n        )\n        self.teardown_request(_teardown_request)\n\n\nclass FlaskInstrumentor(BaseInstrumentor):\n    # pylint: disable=protected-access,attribute-defined-outside-init\n    \"\"\"An instrumentor for flask.Flask\n\n    See `BaseInstrumentor`\n    \"\"\"\n\n    def instrumentation_dependencies(self) -> Collection[str]:\n        return _instruments\n\n    def _instrument(self, **kwargs):\n        self._original_flask = flask.Flask\n        request_hook = kwargs.get(\"request_hook\")\n        response_hook = kwargs.get(\"response_hook\")\n        if callable(request_hook):\n            _InstrumentedFlask._request_hook = request_hook\n        if callable(response_hook):\n            _InstrumentedFlask._response_hook = response_hook\n        tracer_provider = kwargs.get(\"tracer_provider\")\n        _InstrumentedFlask._tracer_provider = tracer_provider\n        excluded_urls = kwargs.get(\"excluded_urls\")\n        _InstrumentedFlask._excluded_urls = (\n            _excluded_urls_from_env\n            if excluded_urls is None\n            else parse_excluded_urls(excluded_urls)\n        )\n        flask.Flask = _InstrumentedFlask\n\n    def _uninstrument(self, **kwargs):\n        flask.Flask = self._original_flask\n\n    @staticmethod\n    def instrument_app(\n        app,\n        request_hook=None,\n        response_hook=None,\n        tracer_provider=None,\n        excluded_urls=None,\n    ):\n        if not hasattr(app, \"_is_instrumented_by_opentelemetry\"):\n            app._is_instrumented_by_opentelemetry = False\n\n        if not app._is_instrumented_by_opentelemetry:\n            excluded_urls = (\n                parse_excluded_urls(excluded_urls)\n                if excluded_urls is not None\n                else _excluded_urls_from_env\n            )\n            app._original_wsgi_app = app.wsgi_app\n            app.wsgi_app = _rewrapped_app(\n                app.wsgi_app, response_hook, excluded_urls=excluded_urls\n            )\n\n            tracer = trace.get_tracer(__name__, __version__, tracer_provider)\n\n            _before_request = _wrapped_before_request(\n                request_hook,\n                tracer,\n                excluded_urls=excluded_urls,\n            )\n            app._before_request = _before_request\n            app.before_request(_before_request)\n\n            _teardown_request = _wrapped_teardown_request(\n                excluded_urls=excluded_urls,\n            )\n            app._teardown_request = _teardown_request\n            app.teardown_request(_teardown_request)\n            app._is_instrumented_by_opentelemetry = True\n        else:\n            _logger.warning(\n                \"Attempting to instrument Flask app while already instrumented\"\n            )\n\n    @staticmethod\n    def uninstrument_app(app):\n        if hasattr(app, \"_original_wsgi_app\"):\n            app.wsgi_app = app._original_wsgi_app\n\n            # FIXME add support for other Flask blueprints that are not None\n            app.before_request_funcs[None].remove(app._before_request)\n            app.teardown_request_funcs[None].remove(app._teardown_request)\n            del app._original_wsgi_app\n            app._is_instrumented_by_opentelemetry = False\n        else:\n            _logger.warning(\n                \"Attempting to uninstrument Flask \"\n                \"app while already uninstrumented\"\n            )\n", "path": "instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py"}]}