We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
is_instrumented_by_opentelemetry is not set in _InstrumentedFastAPI class
**Steps to reproduce**
If `instrument_app` function is called for already instrumented object of `_InstrumentedFastAPI` then it will add Opentelemtery middleware again in the `user_middleware` list as `is_instrumented_by_opentelemetry` is not set in the `__init__` function of `_InstrumentedFastAPI`.

**What is the expected behavior?**
If a fastapi app is already instrumented, i.e., it already has otel middleware in it then it shouldn't add another one.

**What is the actual behavior?**
Multiple otel middlewares are added.

--- 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-fastapi/src/opentelemetry/instrumentation/fastapi/__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 """
16 Usage
17 -----
18 
19 .. code-block:: python
20 
21     import fastapi
22     from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
23 
24     app = fastapi.FastAPI()
25 
26     @app.get("/foobar")
27     async def foobar():
28         return {"message": "hello world"}
29 
30     FastAPIInstrumentor.instrument_app(app)
31 
32 Configuration
33 -------------
34 
35 Exclude lists
36 *************
37 To exclude certain URLs from being tracked, set the environment variable ``OTEL_PYTHON_FASTAPI_EXCLUDED_URLS``
38 (or ``OTEL_PYTHON_EXCLUDED_URLS`` as fallback) with comma delimited regexes representing which URLs to exclude.
39 
40 For example,
41 
42 ::
43 
44     export OTEL_PYTHON_FASTAPI_EXCLUDED_URLS="client/.*/info,healthcheck"
45 
46 will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``.
47 
48 You can also pass the comma delimited regexes to the ``instrument_app`` method directly:
49 
50 .. code-block:: python
51 
52     FastAPIInstrumentor.instrument_app(app, excluded_urls="client/.*/info,healthcheck")
53 
54 Request/Response hooks
55 **********************
56 
57 Utilize request/response hooks to execute custom logic to be performed before/after performing a request. The server request hook takes in a server span and ASGI
58 scope object for every incoming request. The client request hook is called with the internal span and an ASGI scope which is sent as a dictionary for when the method receive is called.
59 The client response hook is called with the internal span and an ASGI event which is sent as a dictionary for when the method send is called.
60 
61 .. code-block:: python
62 
63     def server_request_hook(span: Span, scope: dict):
64         if span and span.is_recording():
65             span.set_attribute("custom_user_attribute_from_request_hook", "some-value")
66 
67     def client_request_hook(span: Span, scope: dict):
68         if span and span.is_recording():
69             span.set_attribute("custom_user_attribute_from_client_request_hook", "some-value")
70 
71     def client_response_hook(span: Span, message: dict):
72         if span and span.is_recording():
73             span.set_attribute("custom_user_attribute_from_response_hook", "some-value")
74 
75    FastAPIInstrumentor().instrument(server_request_hook=server_request_hook, client_request_hook=client_request_hook, client_response_hook=client_response_hook)
76 
77 Capture HTTP request and response headers
78 *****************************************
79 You can configure the agent to capture predefined HTTP headers as span attributes, according to the `semantic convention <https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-request-and-response-headers>`_.
80 
81 Request headers
82 ***************
83 To capture predefined HTTP request headers as span attributes, set the environment variable ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST``
84 to a comma-separated list of HTTP header names.
85 
86 For example,
87 
88 ::
89 
90     export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="content-type,custom_request_header"
91 
92 will extract ``content-type`` and ``custom_request_header`` from request headers and add them as span attributes.
93 
94 It is recommended that you should give the correct names of the headers to be captured in the environment variable.
95 Request header names in fastapi are case insensitive. So, giving header name as ``CUStom-Header`` in environment variable will be able capture header with name ``custom-header``.
96 
97 The name of the added span attribute will follow the format ``http.request.header.<header_name>`` where ``<header_name>`` being the normalized HTTP header name (lowercase, with - characters replaced by _ ).
98 The value of the attribute will be single item list containing all the header values.
99 
100 Example of the added span attribute,
101 ``http.request.header.custom_request_header = ["<value1>,<value2>"]``
102 
103 Response headers
104 ****************
105 To capture predefined HTTP response headers as span attributes, set the environment variable ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE``
106 to a comma-separated list of HTTP header names.
107 
108 For example,
109 
110 ::
111 
112     export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="content-type,custom_response_header"
113 
114 will extract ``content-type`` and ``custom_response_header`` from response headers and add them as span attributes.
115 
116 It is recommended that you should give the correct names of the headers to be captured in the environment variable.
117 Response header names captured in fastapi are case insensitive. So, giving header name as ``CUStomHeader`` in environment variable will be able capture header with name ``customheader``.
118 
119 The name of the added span attribute will follow the format ``http.response.header.<header_name>`` where ``<header_name>`` being the normalized HTTP header name (lowercase, with - characters replaced by _ ).
120 The value of the attribute will be single item list containing all the header values.
121 
122 Example of the added span attribute,
123 ``http.response.header.custom_response_header = ["<value1>,<value2>"]``
124 
125 Note:
126     Environment variable names to capture http headers are still experimental, and thus are subject to change.
127 
128 API
129 ---
130 """
131 import logging
132 import typing
133 from typing import Collection
134 
135 import fastapi
136 from starlette.routing import Match
137 
138 from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware
139 from opentelemetry.instrumentation.asgi.package import _instruments
140 from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
141 from opentelemetry.semconv.trace import SpanAttributes
142 from opentelemetry.trace import Span
143 from opentelemetry.util.http import get_excluded_urls, parse_excluded_urls
144 
145 _excluded_urls_from_env = get_excluded_urls("FASTAPI")
146 _logger = logging.getLogger(__name__)
147 
148 _ServerRequestHookT = typing.Optional[typing.Callable[[Span, dict], None]]
149 _ClientRequestHookT = typing.Optional[typing.Callable[[Span, dict], None]]
150 _ClientResponseHookT = typing.Optional[typing.Callable[[Span, dict], None]]
151 
152 
153 class FastAPIInstrumentor(BaseInstrumentor):
154     """An instrumentor for FastAPI
155 
156     See `BaseInstrumentor`
157     """
158 
159     _original_fastapi = None
160 
161     @staticmethod
162     def instrument_app(
163         app: fastapi.FastAPI,
164         server_request_hook: _ServerRequestHookT = None,
165         client_request_hook: _ClientRequestHookT = None,
166         client_response_hook: _ClientResponseHookT = None,
167         tracer_provider=None,
168         excluded_urls=None,
169     ):
170         """Instrument an uninstrumented FastAPI application."""
171         if not hasattr(app, "_is_instrumented_by_opentelemetry"):
172             app._is_instrumented_by_opentelemetry = False
173 
174         if not getattr(app, "_is_instrumented_by_opentelemetry", False):
175             if excluded_urls is None:
176                 excluded_urls = _excluded_urls_from_env
177             else:
178                 excluded_urls = parse_excluded_urls(excluded_urls)
179 
180             app.add_middleware(
181                 OpenTelemetryMiddleware,
182                 excluded_urls=excluded_urls,
183                 default_span_details=_get_route_details,
184                 server_request_hook=server_request_hook,
185                 client_request_hook=client_request_hook,
186                 client_response_hook=client_response_hook,
187                 tracer_provider=tracer_provider,
188             )
189             app._is_instrumented_by_opentelemetry = True
190         else:
191             _logger.warning(
192                 "Attempting to instrument FastAPI app while already instrumented"
193             )
194 
195     @staticmethod
196     def uninstrument_app(app: fastapi.FastAPI):
197         app.user_middleware = [
198             x
199             for x in app.user_middleware
200             if x.cls is not OpenTelemetryMiddleware
201         ]
202         app.middleware_stack = app.build_middleware_stack()
203         app._is_instrumented_by_opentelemetry = False
204 
205     def instrumentation_dependencies(self) -> Collection[str]:
206         return _instruments
207 
208     def _instrument(self, **kwargs):
209         self._original_fastapi = fastapi.FastAPI
210         _InstrumentedFastAPI._tracer_provider = kwargs.get("tracer_provider")
211         _InstrumentedFastAPI._server_request_hook = kwargs.get(
212             "server_request_hook"
213         )
214         _InstrumentedFastAPI._client_request_hook = kwargs.get(
215             "client_request_hook"
216         )
217         _InstrumentedFastAPI._client_response_hook = kwargs.get(
218             "client_response_hook"
219         )
220         _excluded_urls = kwargs.get("excluded_urls")
221         _InstrumentedFastAPI._excluded_urls = (
222             _excluded_urls_from_env
223             if _excluded_urls is None
224             else parse_excluded_urls(_excluded_urls)
225         )
226         fastapi.FastAPI = _InstrumentedFastAPI
227 
228     def _uninstrument(self, **kwargs):
229         fastapi.FastAPI = self._original_fastapi
230 
231 
232 class _InstrumentedFastAPI(fastapi.FastAPI):
233     _tracer_provider = None
234     _excluded_urls = None
235     _server_request_hook: _ServerRequestHookT = None
236     _client_request_hook: _ClientRequestHookT = None
237     _client_response_hook: _ClientResponseHookT = None
238 
239     def __init__(self, *args, **kwargs):
240         super().__init__(*args, **kwargs)
241         self.add_middleware(
242             OpenTelemetryMiddleware,
243             excluded_urls=_InstrumentedFastAPI._excluded_urls,
244             default_span_details=_get_route_details,
245             server_request_hook=_InstrumentedFastAPI._server_request_hook,
246             client_request_hook=_InstrumentedFastAPI._client_request_hook,
247             client_response_hook=_InstrumentedFastAPI._client_response_hook,
248             tracer_provider=_InstrumentedFastAPI._tracer_provider,
249         )
250 
251 
252 def _get_route_details(scope):
253     """Callback to retrieve the fastapi route being served.
254 
255     TODO: there is currently no way to retrieve http.route from
256     a starlette application from scope.
257 
258     See: https://github.com/encode/starlette/pull/804
259     """
260     app = scope["app"]
261     route = None
262     for starlette_route in app.routes:
263         match, _ = starlette_route.matches(scope)
264         if match == Match.FULL:
265             route = starlette_route.path
266             break
267         if match == Match.PARTIAL:
268             route = starlette_route.path
269     # method only exists for http, if websocket
270     # leave it blank.
271     span_name = route or scope.get("method", "")
272     attributes = {}
273     if route:
274         attributes[SpanAttributes.HTTP_ROUTE] = route
275     return span_name, attributes
276
```
--- 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-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py
--- a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py
+++ b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py
@@ -247,6 +247,7 @@
             client_response_hook=_InstrumentedFastAPI._client_response_hook,
             tracer_provider=_InstrumentedFastAPI._tracer_provider,
         )
+        self._is_instrumented_by_opentelemetry = True
 
 
 def _get_route_details(scope):

{"golden_diff": "diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py\n--- a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py\n+++ b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py\n@@ -247,6 +247,7 @@\n             client_response_hook=_InstrumentedFastAPI._client_response_hook,\n             tracer_provider=_InstrumentedFastAPI._tracer_provider,\n         )\n+        self._is_instrumented_by_opentelemetry = True\n \n \n def _get_route_details(scope):\n", "issue": "is_instrumented_by_opentelemetry is not set in _InstrumentedFastAPI class\n**Steps to reproduce**\r\nIf `instrument_app` function is called for already instrumented object of `_InstrumentedFastAPI` then it will add Opentelemtery middleware again in the `user_middleware` list as `is_instrumented_by_opentelemetry` is not set in the `__init__` function of `_InstrumentedFastAPI`.\r\n\r\n**What is the expected behavior?**\r\nIf a fastapi app is already instrumented, i.e., it already has otel middleware in it then it shouldn't add another one.\r\n\r\n**What is the actual behavior?**\r\nMultiple otel middlewares are added.\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\"\"\"\nUsage\n-----\n\n.. code-block:: python\n\n    import fastapi\n    from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor\n\n    app = fastapi.FastAPI()\n\n    @app.get(\"/foobar\")\n    async def foobar():\n        return {\"message\": \"hello world\"}\n\n    FastAPIInstrumentor.instrument_app(app)\n\nConfiguration\n-------------\n\nExclude lists\n*************\nTo exclude certain URLs from being tracked, set the environment variable ``OTEL_PYTHON_FASTAPI_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_FASTAPI_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    FastAPIInstrumentor.instrument_app(app, excluded_urls=\"client/.*/info,healthcheck\")\n\nRequest/Response hooks\n**********************\n\nUtilize request/response hooks to execute custom logic to be performed before/after performing a request. The server request hook takes in a server span and ASGI\nscope object for every incoming request. The client request hook is called with the internal span and an ASGI scope which is sent as a dictionary for when the method receive is called.\nThe client response hook is called with the internal span and an ASGI event which is sent as a dictionary for when the method send is called.\n\n.. code-block:: python\n\n    def server_request_hook(span: Span, scope: dict):\n        if span and span.is_recording():\n            span.set_attribute(\"custom_user_attribute_from_request_hook\", \"some-value\")\n\n    def client_request_hook(span: Span, scope: dict):\n        if span and span.is_recording():\n            span.set_attribute(\"custom_user_attribute_from_client_request_hook\", \"some-value\")\n\n    def client_response_hook(span: Span, message: dict):\n        if span and span.is_recording():\n            span.set_attribute(\"custom_user_attribute_from_response_hook\", \"some-value\")\n\n   FastAPIInstrumentor().instrument(server_request_hook=server_request_hook, client_request_hook=client_request_hook, client_response_hook=client_response_hook)\n\nCapture HTTP request and response headers\n*****************************************\nYou can configure the agent to capture predefined HTTP headers as span attributes, according to the `semantic convention <https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-request-and-response-headers>`_.\n\nRequest headers\n***************\nTo capture predefined HTTP request headers as span attributes, set the environment variable ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST``\nto a comma-separated list of HTTP header names.\n\nFor example,\n\n::\n\n    export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST=\"content-type,custom_request_header\"\n\nwill extract ``content-type`` and ``custom_request_header`` from request headers and add them as span attributes.\n\nIt is recommended that you should give the correct names of the headers to be captured in the environment variable.\nRequest header names in fastapi are case insensitive. So, giving header name as ``CUStom-Header`` in environment variable will be able capture header with name ``custom-header``.\n\nThe name of the added span attribute will follow the format ``http.request.header.<header_name>`` where ``<header_name>`` being the normalized HTTP header name (lowercase, with - characters replaced by _ ).\nThe value of the attribute will be single item list containing all the header values.\n\nExample of the added span attribute,\n``http.request.header.custom_request_header = [\"<value1>,<value2>\"]``\n\nResponse headers\n****************\nTo capture predefined HTTP response headers as span attributes, set the environment variable ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE``\nto a comma-separated list of HTTP header names.\n\nFor example,\n\n::\n\n    export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE=\"content-type,custom_response_header\"\n\nwill extract ``content-type`` and ``custom_response_header`` from response headers and add them as span attributes.\n\nIt is recommended that you should give the correct names of the headers to be captured in the environment variable.\nResponse header names captured in fastapi are case insensitive. So, giving header name as ``CUStomHeader`` in environment variable will be able capture header with name ``customheader``.\n\nThe name of the added span attribute will follow the format ``http.response.header.<header_name>`` where ``<header_name>`` being the normalized HTTP header name (lowercase, with - characters replaced by _ ).\nThe value of the attribute will be single item list containing all the header values.\n\nExample of the added span attribute,\n``http.response.header.custom_response_header = [\"<value1>,<value2>\"]``\n\nNote:\n    Environment variable names to capture http headers are still experimental, and thus are subject to change.\n\nAPI\n---\n\"\"\"\nimport logging\nimport typing\nfrom typing import Collection\n\nimport fastapi\nfrom starlette.routing import Match\n\nfrom opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware\nfrom opentelemetry.instrumentation.asgi.package import _instruments\nfrom opentelemetry.instrumentation.instrumentor import BaseInstrumentor\nfrom opentelemetry.semconv.trace import SpanAttributes\nfrom opentelemetry.trace import Span\nfrom opentelemetry.util.http import get_excluded_urls, parse_excluded_urls\n\n_excluded_urls_from_env = get_excluded_urls(\"FASTAPI\")\n_logger = logging.getLogger(__name__)\n\n_ServerRequestHookT = typing.Optional[typing.Callable[[Span, dict], None]]\n_ClientRequestHookT = typing.Optional[typing.Callable[[Span, dict], None]]\n_ClientResponseHookT = typing.Optional[typing.Callable[[Span, dict], None]]\n\n\nclass FastAPIInstrumentor(BaseInstrumentor):\n    \"\"\"An instrumentor for FastAPI\n\n    See `BaseInstrumentor`\n    \"\"\"\n\n    _original_fastapi = None\n\n    @staticmethod\n    def instrument_app(\n        app: fastapi.FastAPI,\n        server_request_hook: _ServerRequestHookT = None,\n        client_request_hook: _ClientRequestHookT = None,\n        client_response_hook: _ClientResponseHookT = None,\n        tracer_provider=None,\n        excluded_urls=None,\n    ):\n        \"\"\"Instrument an uninstrumented FastAPI application.\"\"\"\n        if not hasattr(app, \"_is_instrumented_by_opentelemetry\"):\n            app._is_instrumented_by_opentelemetry = False\n\n        if not getattr(app, \"_is_instrumented_by_opentelemetry\", False):\n            if excluded_urls is None:\n                excluded_urls = _excluded_urls_from_env\n            else:\n                excluded_urls = parse_excluded_urls(excluded_urls)\n\n            app.add_middleware(\n                OpenTelemetryMiddleware,\n                excluded_urls=excluded_urls,\n                default_span_details=_get_route_details,\n                server_request_hook=server_request_hook,\n                client_request_hook=client_request_hook,\n                client_response_hook=client_response_hook,\n                tracer_provider=tracer_provider,\n            )\n            app._is_instrumented_by_opentelemetry = True\n        else:\n            _logger.warning(\n                \"Attempting to instrument FastAPI app while already instrumented\"\n            )\n\n    @staticmethod\n    def uninstrument_app(app: fastapi.FastAPI):\n        app.user_middleware = [\n            x\n            for x in app.user_middleware\n            if x.cls is not OpenTelemetryMiddleware\n        ]\n        app.middleware_stack = app.build_middleware_stack()\n        app._is_instrumented_by_opentelemetry = False\n\n    def instrumentation_dependencies(self) -> Collection[str]:\n        return _instruments\n\n    def _instrument(self, **kwargs):\n        self._original_fastapi = fastapi.FastAPI\n        _InstrumentedFastAPI._tracer_provider = kwargs.get(\"tracer_provider\")\n        _InstrumentedFastAPI._server_request_hook = kwargs.get(\n            \"server_request_hook\"\n        )\n        _InstrumentedFastAPI._client_request_hook = kwargs.get(\n            \"client_request_hook\"\n        )\n        _InstrumentedFastAPI._client_response_hook = kwargs.get(\n            \"client_response_hook\"\n        )\n        _excluded_urls = kwargs.get(\"excluded_urls\")\n        _InstrumentedFastAPI._excluded_urls = (\n            _excluded_urls_from_env\n            if _excluded_urls is None\n            else parse_excluded_urls(_excluded_urls)\n        )\n        fastapi.FastAPI = _InstrumentedFastAPI\n\n    def _uninstrument(self, **kwargs):\n        fastapi.FastAPI = self._original_fastapi\n\n\nclass _InstrumentedFastAPI(fastapi.FastAPI):\n    _tracer_provider = None\n    _excluded_urls = None\n    _server_request_hook: _ServerRequestHookT = None\n    _client_request_hook: _ClientRequestHookT = None\n    _client_response_hook: _ClientResponseHookT = None\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.add_middleware(\n            OpenTelemetryMiddleware,\n            excluded_urls=_InstrumentedFastAPI._excluded_urls,\n            default_span_details=_get_route_details,\n            server_request_hook=_InstrumentedFastAPI._server_request_hook,\n            client_request_hook=_InstrumentedFastAPI._client_request_hook,\n            client_response_hook=_InstrumentedFastAPI._client_response_hook,\n            tracer_provider=_InstrumentedFastAPI._tracer_provider,\n        )\n\n\ndef _get_route_details(scope):\n    \"\"\"Callback to retrieve the fastapi route being served.\n\n    TODO: there is currently no way to retrieve http.route from\n    a starlette application from scope.\n\n    See: https://github.com/encode/starlette/pull/804\n    \"\"\"\n    app = scope[\"app\"]\n    route = None\n    for starlette_route in app.routes:\n        match, _ = starlette_route.matches(scope)\n        if match == Match.FULL:\n            route = starlette_route.path\n            break\n        if match == Match.PARTIAL:\n            route = starlette_route.path\n    # method only exists for http, if websocket\n    # leave it blank.\n    span_name = route or scope.get(\"method\", \"\")\n    attributes = {}\n    if route:\n        attributes[SpanAttributes.HTTP_ROUTE] = route\n    return span_name, attributes\n", "path": "instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__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\"\"\"\nUsage\n-----\n\n.. code-block:: python\n\n    import fastapi\n    from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor\n\n    app = fastapi.FastAPI()\n\n    @app.get(\"/foobar\")\n    async def foobar():\n        return {\"message\": \"hello world\"}\n\n    FastAPIInstrumentor.instrument_app(app)\n\nConfiguration\n-------------\n\nExclude lists\n*************\nTo exclude certain URLs from being tracked, set the environment variable ``OTEL_PYTHON_FASTAPI_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_FASTAPI_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    FastAPIInstrumentor.instrument_app(app, excluded_urls=\"client/.*/info,healthcheck\")\n\nRequest/Response hooks\n**********************\n\nUtilize request/response hooks to execute custom logic to be performed before/after performing a request. The server request hook takes in a server span and ASGI\nscope object for every incoming request. The client request hook is called with the internal span and an ASGI scope which is sent as a dictionary for when the method receive is called.\nThe client response hook is called with the internal span and an ASGI event which is sent as a dictionary for when the method send is called.\n\n.. code-block:: python\n\n    def server_request_hook(span: Span, scope: dict):\n        if span and span.is_recording():\n            span.set_attribute(\"custom_user_attribute_from_request_hook\", \"some-value\")\n\n    def client_request_hook(span: Span, scope: dict):\n        if span and span.is_recording():\n            span.set_attribute(\"custom_user_attribute_from_client_request_hook\", \"some-value\")\n\n    def client_response_hook(span: Span, message: dict):\n        if span and span.is_recording():\n            span.set_attribute(\"custom_user_attribute_from_response_hook\", \"some-value\")\n\n   FastAPIInstrumentor().instrument(server_request_hook=server_request_hook, client_request_hook=client_request_hook, client_response_hook=client_response_hook)\n\nCapture HTTP request and response headers\n*****************************************\nYou can configure the agent to capture predefined HTTP headers as span attributes, according to the `semantic convention <https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-request-and-response-headers>`_.\n\nRequest headers\n***************\nTo capture predefined HTTP request headers as span attributes, set the environment variable ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST``\nto a comma-separated list of HTTP header names.\n\nFor example,\n\n::\n\n    export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST=\"content-type,custom_request_header\"\n\nwill extract ``content-type`` and ``custom_request_header`` from request headers and add them as span attributes.\n\nIt is recommended that you should give the correct names of the headers to be captured in the environment variable.\nRequest header names in fastapi are case insensitive. So, giving header name as ``CUStom-Header`` in environment variable will be able capture header with name ``custom-header``.\n\nThe name of the added span attribute will follow the format ``http.request.header.<header_name>`` where ``<header_name>`` being the normalized HTTP header name (lowercase, with - characters replaced by _ ).\nThe value of the attribute will be single item list containing all the header values.\n\nExample of the added span attribute,\n``http.request.header.custom_request_header = [\"<value1>,<value2>\"]``\n\nResponse headers\n****************\nTo capture predefined HTTP response headers as span attributes, set the environment variable ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE``\nto a comma-separated list of HTTP header names.\n\nFor example,\n\n::\n\n    export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE=\"content-type,custom_response_header\"\n\nwill extract ``content-type`` and ``custom_response_header`` from response headers and add them as span attributes.\n\nIt is recommended that you should give the correct names of the headers to be captured in the environment variable.\nResponse header names captured in fastapi are case insensitive. So, giving header name as ``CUStomHeader`` in environment variable will be able capture header with name ``customheader``.\n\nThe name of the added span attribute will follow the format ``http.response.header.<header_name>`` where ``<header_name>`` being the normalized HTTP header name (lowercase, with - characters replaced by _ ).\nThe value of the attribute will be single item list containing all the header values.\n\nExample of the added span attribute,\n``http.response.header.custom_response_header = [\"<value1>,<value2>\"]``\n\nNote:\n    Environment variable names to capture http headers are still experimental, and thus are subject to change.\n\nAPI\n---\n\"\"\"\nimport logging\nimport typing\nfrom typing import Collection\n\nimport fastapi\nfrom starlette.routing import Match\n\nfrom opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware\nfrom opentelemetry.instrumentation.asgi.package import _instruments\nfrom opentelemetry.instrumentation.instrumentor import BaseInstrumentor\nfrom opentelemetry.semconv.trace import SpanAttributes\nfrom opentelemetry.trace import Span\nfrom opentelemetry.util.http import get_excluded_urls, parse_excluded_urls\n\n_excluded_urls_from_env = get_excluded_urls(\"FASTAPI\")\n_logger = logging.getLogger(__name__)\n\n_ServerRequestHookT = typing.Optional[typing.Callable[[Span, dict], None]]\n_ClientRequestHookT = typing.Optional[typing.Callable[[Span, dict], None]]\n_ClientResponseHookT = typing.Optional[typing.Callable[[Span, dict], None]]\n\n\nclass FastAPIInstrumentor(BaseInstrumentor):\n    \"\"\"An instrumentor for FastAPI\n\n    See `BaseInstrumentor`\n    \"\"\"\n\n    _original_fastapi = None\n\n    @staticmethod\n    def instrument_app(\n        app: fastapi.FastAPI,\n        server_request_hook: _ServerRequestHookT = None,\n        client_request_hook: _ClientRequestHookT = None,\n        client_response_hook: _ClientResponseHookT = None,\n        tracer_provider=None,\n        excluded_urls=None,\n    ):\n        \"\"\"Instrument an uninstrumented FastAPI application.\"\"\"\n        if not hasattr(app, \"_is_instrumented_by_opentelemetry\"):\n            app._is_instrumented_by_opentelemetry = False\n\n        if not getattr(app, \"_is_instrumented_by_opentelemetry\", False):\n            if excluded_urls is None:\n                excluded_urls = _excluded_urls_from_env\n            else:\n                excluded_urls = parse_excluded_urls(excluded_urls)\n\n            app.add_middleware(\n                OpenTelemetryMiddleware,\n                excluded_urls=excluded_urls,\n                default_span_details=_get_route_details,\n                server_request_hook=server_request_hook,\n                client_request_hook=client_request_hook,\n                client_response_hook=client_response_hook,\n                tracer_provider=tracer_provider,\n            )\n            app._is_instrumented_by_opentelemetry = True\n        else:\n            _logger.warning(\n                \"Attempting to instrument FastAPI app while already instrumented\"\n            )\n\n    @staticmethod\n    def uninstrument_app(app: fastapi.FastAPI):\n        app.user_middleware = [\n            x\n            for x in app.user_middleware\n            if x.cls is not OpenTelemetryMiddleware\n        ]\n        app.middleware_stack = app.build_middleware_stack()\n        app._is_instrumented_by_opentelemetry = False\n\n    def instrumentation_dependencies(self) -> Collection[str]:\n        return _instruments\n\n    def _instrument(self, **kwargs):\n        self._original_fastapi = fastapi.FastAPI\n        _InstrumentedFastAPI._tracer_provider = kwargs.get(\"tracer_provider\")\n        _InstrumentedFastAPI._server_request_hook = kwargs.get(\n            \"server_request_hook\"\n        )\n        _InstrumentedFastAPI._client_request_hook = kwargs.get(\n            \"client_request_hook\"\n        )\n        _InstrumentedFastAPI._client_response_hook = kwargs.get(\n            \"client_response_hook\"\n        )\n        _excluded_urls = kwargs.get(\"excluded_urls\")\n        _InstrumentedFastAPI._excluded_urls = (\n            _excluded_urls_from_env\n            if _excluded_urls is None\n            else parse_excluded_urls(_excluded_urls)\n        )\n        fastapi.FastAPI = _InstrumentedFastAPI\n\n    def _uninstrument(self, **kwargs):\n        fastapi.FastAPI = self._original_fastapi\n\n\nclass _InstrumentedFastAPI(fastapi.FastAPI):\n    _tracer_provider = None\n    _excluded_urls = None\n    _server_request_hook: _ServerRequestHookT = None\n    _client_request_hook: _ClientRequestHookT = None\n    _client_response_hook: _ClientResponseHookT = None\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.add_middleware(\n            OpenTelemetryMiddleware,\n            excluded_urls=_InstrumentedFastAPI._excluded_urls,\n            default_span_details=_get_route_details,\n            server_request_hook=_InstrumentedFastAPI._server_request_hook,\n            client_request_hook=_InstrumentedFastAPI._client_request_hook,\n            client_response_hook=_InstrumentedFastAPI._client_response_hook,\n            tracer_provider=_InstrumentedFastAPI._tracer_provider,\n        )\n        self._is_instrumented_by_opentelemetry = True\n\n\ndef _get_route_details(scope):\n    \"\"\"Callback to retrieve the fastapi route being served.\n\n    TODO: there is currently no way to retrieve http.route from\n    a starlette application from scope.\n\n    See: https://github.com/encode/starlette/pull/804\n    \"\"\"\n    app = scope[\"app\"]\n    route = None\n    for starlette_route in app.routes:\n        match, _ = starlette_route.matches(scope)\n        if match == Match.FULL:\n            route = starlette_route.path\n            break\n        if match == Match.PARTIAL:\n            route = starlette_route.path\n    # method only exists for http, if websocket\n    # leave it blank.\n    span_name = route or scope.get(\"method\", \"\")\n    attributes = {}\n    if route:\n        attributes[SpanAttributes.HTTP_ROUTE] = route\n    return span_name, attributes\n", "path": "instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py"}]}