We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Compatible with web.py?
Is it possible to use  opentelemetry-insrumentation-wsgi with web.py framework? If yes, some examples would be appreciated.

--- 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-wsgi/src/opentelemetry/instrumentation/wsgi/__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 This library provides a WSGI middleware that can be used on any WSGI framework
16 (such as Django / Flask) to track requests timing through OpenTelemetry.
17 
18 Usage (Flask)
19 -------------
20 
21 .. code-block:: python
22 
23     from flask import Flask
24     from opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware
25 
26     app = Flask(__name__)
27     app.wsgi_app = OpenTelemetryMiddleware(app.wsgi_app)
28 
29     @app.route("/")
30     def hello():
31         return "Hello!"
32 
33     if __name__ == "__main__":
34         app.run(debug=True)
35 
36 
37 Usage (Django)
38 --------------
39 
40 Modify the application's ``wsgi.py`` file as shown below.
41 
42 .. code-block:: python
43 
44     import os
45     from opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware
46     from django.core.wsgi import get_wsgi_application
47 
48     os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
49 
50     application = get_wsgi_application()
51     application = OpenTelemetryMiddleware(application)
52 
53 API
54 ---
55 """
56 
57 import functools
58 import typing
59 import wsgiref.util as wsgiref_util
60 
61 from opentelemetry import context, trace
62 from opentelemetry.instrumentation.utils import http_status_to_status_code
63 from opentelemetry.instrumentation.wsgi.version import __version__
64 from opentelemetry.propagate import extract
65 from opentelemetry.propagators.textmap import Getter
66 from opentelemetry.semconv.trace import SpanAttributes
67 from opentelemetry.trace.status import Status, StatusCode
68 from opentelemetry.util.http import remove_url_credentials
69 
70 _HTTP_VERSION_PREFIX = "HTTP/"
71 _CARRIER_KEY_PREFIX = "HTTP_"
72 _CARRIER_KEY_PREFIX_LEN = len(_CARRIER_KEY_PREFIX)
73 
74 
75 class WSGIGetter(Getter):
76     def get(
77         self, carrier: dict, key: str
78     ) -> typing.Optional[typing.List[str]]:
79         """Getter implementation to retrieve a HTTP header value from the
80              PEP3333-conforming WSGI environ
81 
82         Args:
83              carrier: WSGI environ object
84              key: header name in environ object
85          Returns:
86              A list with a single string with the header value if it exists,
87              else None.
88         """
89         environ_key = "HTTP_" + key.upper().replace("-", "_")
90         value = carrier.get(environ_key)
91         if value is not None:
92             return [value]
93         return None
94 
95     def keys(self, carrier):
96         return [
97             key[_CARRIER_KEY_PREFIX_LEN:].lower().replace("_", "-")
98             for key in carrier
99             if key.startswith(_CARRIER_KEY_PREFIX)
100         ]
101 
102 
103 wsgi_getter = WSGIGetter()
104 
105 
106 def setifnotnone(dic, key, value):
107     if value is not None:
108         dic[key] = value
109 
110 
111 def collect_request_attributes(environ):
112     """Collects HTTP request attributes from the PEP3333-conforming
113     WSGI environ and returns a dictionary to be used as span creation attributes."""
114 
115     result = {
116         SpanAttributes.HTTP_METHOD: environ.get("REQUEST_METHOD"),
117         SpanAttributes.HTTP_SERVER_NAME: environ.get("SERVER_NAME"),
118         SpanAttributes.HTTP_SCHEME: environ.get("wsgi.url_scheme"),
119     }
120 
121     host_port = environ.get("SERVER_PORT")
122     if host_port is not None and not host_port == "":
123         result.update({SpanAttributes.NET_HOST_PORT: int(host_port)})
124 
125     setifnotnone(result, SpanAttributes.HTTP_HOST, environ.get("HTTP_HOST"))
126     target = environ.get("RAW_URI")
127     if target is None:  # Note: `"" or None is None`
128         target = environ.get("REQUEST_URI")
129     if target is not None:
130         result[SpanAttributes.HTTP_TARGET] = target
131     else:
132         result[SpanAttributes.HTTP_URL] = remove_url_credentials(
133             wsgiref_util.request_uri(environ)
134         )
135 
136     remote_addr = environ.get("REMOTE_ADDR")
137     if remote_addr:
138         result[SpanAttributes.NET_PEER_IP] = remote_addr
139     remote_host = environ.get("REMOTE_HOST")
140     if remote_host and remote_host != remote_addr:
141         result[SpanAttributes.NET_PEER_NAME] = remote_host
142 
143     user_agent = environ.get("HTTP_USER_AGENT")
144     if user_agent is not None and len(user_agent) > 0:
145         result[SpanAttributes.HTTP_USER_AGENT] = user_agent
146 
147     setifnotnone(
148         result, SpanAttributes.NET_PEER_PORT, environ.get("REMOTE_PORT")
149     )
150     flavor = environ.get("SERVER_PROTOCOL", "")
151     if flavor.upper().startswith(_HTTP_VERSION_PREFIX):
152         flavor = flavor[len(_HTTP_VERSION_PREFIX) :]
153     if flavor:
154         result[SpanAttributes.HTTP_FLAVOR] = flavor
155 
156     return result
157 
158 
159 def add_response_attributes(
160     span, start_response_status, response_headers
161 ):  # pylint: disable=unused-argument
162     """Adds HTTP response attributes to span using the arguments
163     passed to a PEP3333-conforming start_response callable."""
164     if not span.is_recording():
165         return
166     status_code, _ = start_response_status.split(" ", 1)
167 
168     try:
169         status_code = int(status_code)
170     except ValueError:
171         span.set_status(
172             Status(
173                 StatusCode.ERROR,
174                 "Non-integer HTTP status: " + repr(status_code),
175             )
176         )
177     else:
178         span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, status_code)
179         span.set_status(Status(http_status_to_status_code(status_code)))
180 
181 
182 def get_default_span_name(environ):
183     """Default implementation for name_callback, returns HTTP {METHOD_NAME}."""
184     return "HTTP {}".format(environ.get("REQUEST_METHOD", "")).strip()
185 
186 
187 class OpenTelemetryMiddleware:
188     """The WSGI application middleware.
189 
190     This class is a PEP 3333 conforming WSGI middleware that starts and
191     annotates spans for any requests it is invoked with.
192 
193     Args:
194         wsgi: The WSGI application callable to forward requests to.
195         request_hook: Optional callback which is called with the server span and WSGI
196                       environ object for every incoming request.
197         response_hook: Optional callback which is called with the server span,
198                        WSGI environ, status_code and response_headers for every
199                        incoming request.
200         tracer_provider: Optional tracer provider to use. If omitted the current
201                          globally configured one is used.
202     """
203 
204     def __init__(
205         self, wsgi, request_hook=None, response_hook=None, tracer_provider=None
206     ):
207         self.wsgi = wsgi
208         self.tracer = trace.get_tracer(__name__, __version__, tracer_provider)
209         self.request_hook = request_hook
210         self.response_hook = response_hook
211 
212     @staticmethod
213     def _create_start_response(span, start_response, response_hook):
214         @functools.wraps(start_response)
215         def _start_response(status, response_headers, *args, **kwargs):
216             add_response_attributes(span, status, response_headers)
217             if response_hook:
218                 response_hook(status, response_headers)
219             return start_response(status, response_headers, *args, **kwargs)
220 
221         return _start_response
222 
223     def __call__(self, environ, start_response):
224         """The WSGI application
225 
226         Args:
227             environ: A WSGI environment.
228             start_response: The WSGI start_response callable.
229         """
230 
231         token = context.attach(extract(environ, getter=wsgi_getter))
232 
233         span = self.tracer.start_span(
234             get_default_span_name(environ),
235             kind=trace.SpanKind.SERVER,
236             attributes=collect_request_attributes(environ),
237         )
238 
239         if self.request_hook:
240             self.request_hook(span, environ)
241 
242         response_hook = self.response_hook
243         if response_hook:
244             response_hook = functools.partial(response_hook, span, environ)
245 
246         try:
247             with trace.use_span(span):
248                 start_response = self._create_start_response(
249                     span, start_response, response_hook
250                 )
251                 iterable = self.wsgi(environ, start_response)
252                 return _end_span_after_iterating(
253                     iterable, span, self.tracer, token
254                 )
255         except Exception as ex:
256             if span.is_recording():
257                 span.set_status(Status(StatusCode.ERROR, str(ex)))
258             span.end()
259             context.detach(token)
260             raise
261 
262 
263 # Put this in a subfunction to not delay the call to the wrapped
264 # WSGI application (instrumentation should change the application
265 # behavior as little as possible).
266 def _end_span_after_iterating(iterable, span, tracer, token):
267     try:
268         with trace.use_span(span):
269             for yielded in iterable:
270                 yield yielded
271     finally:
272         close = getattr(iterable, "close", None)
273         if close:
274             close()
275         span.end()
276         context.detach(token)
277 
278 
279 # TODO: inherit from opentelemetry.instrumentation.propagators.Setter
280 
281 
282 class ResponsePropagationSetter:
283     def set(self, carrier, key, value):  # pylint: disable=no-self-use
284         carrier.append((key, value))
285 
286 
287 default_response_propagation_setter = ResponsePropagationSetter()
288
```
--- 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-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py
--- a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py
+++ b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py
@@ -13,7 +13,7 @@
 # limitations under the License.
 """
 This library provides a WSGI middleware that can be used on any WSGI framework
-(such as Django / Flask) to track requests timing through OpenTelemetry.
+(such as Django / Flask / Web.py) to track requests timing through OpenTelemetry.
 
 Usage (Flask)
 -------------
@@ -50,6 +50,35 @@
     application = get_wsgi_application()
     application = OpenTelemetryMiddleware(application)
 
+Usage (Web.py)
+--------------
+
+.. code-block:: python
+
+    import web
+    from opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware
+    from cheroot import wsgi
+
+    urls = ('/', 'index')
+
+
+    class index:
+
+        def GET(self):
+            return "Hello, world!"
+
+
+    if __name__ == "__main__":
+        app = web.application(urls, globals())
+        func = app.wsgifunc()
+
+        func = OpenTelemetryMiddleware(func)
+
+        server = wsgi.WSGIServer(
+            ("localhost", 5100), func, server_name="localhost"
+        )
+        server.start()
+
 API
 ---
 """

{"golden_diff": "diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py\n--- a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py\n+++ b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py\n@@ -13,7 +13,7 @@\n # limitations under the License.\n \"\"\"\n This library provides a WSGI middleware that can be used on any WSGI framework\n-(such as Django / Flask) to track requests timing through OpenTelemetry.\n+(such as Django / Flask / Web.py) to track requests timing through OpenTelemetry.\n \n Usage (Flask)\n -------------\n@@ -50,6 +50,35 @@\n     application = get_wsgi_application()\n     application = OpenTelemetryMiddleware(application)\n \n+Usage (Web.py)\n+--------------\n+\n+.. code-block:: python\n+\n+    import web\n+    from opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware\n+    from cheroot import wsgi\n+\n+    urls = ('/', 'index')\n+\n+\n+    class index:\n+\n+        def GET(self):\n+            return \"Hello, world!\"\n+\n+\n+    if __name__ == \"__main__\":\n+        app = web.application(urls, globals())\n+        func = app.wsgifunc()\n+\n+        func = OpenTelemetryMiddleware(func)\n+\n+        server = wsgi.WSGIServer(\n+            (\"localhost\", 5100), func, server_name=\"localhost\"\n+        )\n+        server.start()\n+\n API\n ---\n \"\"\"\n", "issue": "Compatible with web.py?\nIs it possible to use  opentelemetry-insrumentation-wsgi with web.py framework? If yes, some examples would be appreciated.\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\"\"\"\nThis library provides a WSGI middleware that can be used on any WSGI framework\n(such as Django / Flask) to track requests timing through OpenTelemetry.\n\nUsage (Flask)\n-------------\n\n.. code-block:: python\n\n    from flask import Flask\n    from opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware\n\n    app = Flask(__name__)\n    app.wsgi_app = OpenTelemetryMiddleware(app.wsgi_app)\n\n    @app.route(\"/\")\n    def hello():\n        return \"Hello!\"\n\n    if __name__ == \"__main__\":\n        app.run(debug=True)\n\n\nUsage (Django)\n--------------\n\nModify the application's ``wsgi.py`` file as shown below.\n\n.. code-block:: python\n\n    import os\n    from opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware\n    from django.core.wsgi import get_wsgi_application\n\n    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')\n\n    application = get_wsgi_application()\n    application = OpenTelemetryMiddleware(application)\n\nAPI\n---\n\"\"\"\n\nimport functools\nimport typing\nimport wsgiref.util as wsgiref_util\n\nfrom opentelemetry import context, trace\nfrom opentelemetry.instrumentation.utils import http_status_to_status_code\nfrom opentelemetry.instrumentation.wsgi.version import __version__\nfrom opentelemetry.propagate import extract\nfrom opentelemetry.propagators.textmap import Getter\nfrom opentelemetry.semconv.trace import SpanAttributes\nfrom opentelemetry.trace.status import Status, StatusCode\nfrom opentelemetry.util.http import remove_url_credentials\n\n_HTTP_VERSION_PREFIX = \"HTTP/\"\n_CARRIER_KEY_PREFIX = \"HTTP_\"\n_CARRIER_KEY_PREFIX_LEN = len(_CARRIER_KEY_PREFIX)\n\n\nclass WSGIGetter(Getter):\n    def get(\n        self, carrier: dict, key: str\n    ) -> typing.Optional[typing.List[str]]:\n        \"\"\"Getter implementation to retrieve a HTTP header value from the\n             PEP3333-conforming WSGI environ\n\n        Args:\n             carrier: WSGI environ object\n             key: header name in environ object\n         Returns:\n             A list with a single string with the header value if it exists,\n             else None.\n        \"\"\"\n        environ_key = \"HTTP_\" + key.upper().replace(\"-\", \"_\")\n        value = carrier.get(environ_key)\n        if value is not None:\n            return [value]\n        return None\n\n    def keys(self, carrier):\n        return [\n            key[_CARRIER_KEY_PREFIX_LEN:].lower().replace(\"_\", \"-\")\n            for key in carrier\n            if key.startswith(_CARRIER_KEY_PREFIX)\n        ]\n\n\nwsgi_getter = WSGIGetter()\n\n\ndef setifnotnone(dic, key, value):\n    if value is not None:\n        dic[key] = value\n\n\ndef collect_request_attributes(environ):\n    \"\"\"Collects HTTP request attributes from the PEP3333-conforming\n    WSGI environ and returns a dictionary to be used as span creation attributes.\"\"\"\n\n    result = {\n        SpanAttributes.HTTP_METHOD: environ.get(\"REQUEST_METHOD\"),\n        SpanAttributes.HTTP_SERVER_NAME: environ.get(\"SERVER_NAME\"),\n        SpanAttributes.HTTP_SCHEME: environ.get(\"wsgi.url_scheme\"),\n    }\n\n    host_port = environ.get(\"SERVER_PORT\")\n    if host_port is not None and not host_port == \"\":\n        result.update({SpanAttributes.NET_HOST_PORT: int(host_port)})\n\n    setifnotnone(result, SpanAttributes.HTTP_HOST, environ.get(\"HTTP_HOST\"))\n    target = environ.get(\"RAW_URI\")\n    if target is None:  # Note: `\"\" or None is None`\n        target = environ.get(\"REQUEST_URI\")\n    if target is not None:\n        result[SpanAttributes.HTTP_TARGET] = target\n    else:\n        result[SpanAttributes.HTTP_URL] = remove_url_credentials(\n            wsgiref_util.request_uri(environ)\n        )\n\n    remote_addr = environ.get(\"REMOTE_ADDR\")\n    if remote_addr:\n        result[SpanAttributes.NET_PEER_IP] = remote_addr\n    remote_host = environ.get(\"REMOTE_HOST\")\n    if remote_host and remote_host != remote_addr:\n        result[SpanAttributes.NET_PEER_NAME] = remote_host\n\n    user_agent = environ.get(\"HTTP_USER_AGENT\")\n    if user_agent is not None and len(user_agent) > 0:\n        result[SpanAttributes.HTTP_USER_AGENT] = user_agent\n\n    setifnotnone(\n        result, SpanAttributes.NET_PEER_PORT, environ.get(\"REMOTE_PORT\")\n    )\n    flavor = environ.get(\"SERVER_PROTOCOL\", \"\")\n    if flavor.upper().startswith(_HTTP_VERSION_PREFIX):\n        flavor = flavor[len(_HTTP_VERSION_PREFIX) :]\n    if flavor:\n        result[SpanAttributes.HTTP_FLAVOR] = flavor\n\n    return result\n\n\ndef add_response_attributes(\n    span, start_response_status, response_headers\n):  # pylint: disable=unused-argument\n    \"\"\"Adds HTTP response attributes to span using the arguments\n    passed to a PEP3333-conforming start_response callable.\"\"\"\n    if not span.is_recording():\n        return\n    status_code, _ = start_response_status.split(\" \", 1)\n\n    try:\n        status_code = int(status_code)\n    except ValueError:\n        span.set_status(\n            Status(\n                StatusCode.ERROR,\n                \"Non-integer HTTP status: \" + repr(status_code),\n            )\n        )\n    else:\n        span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, status_code)\n        span.set_status(Status(http_status_to_status_code(status_code)))\n\n\ndef get_default_span_name(environ):\n    \"\"\"Default implementation for name_callback, returns HTTP {METHOD_NAME}.\"\"\"\n    return \"HTTP {}\".format(environ.get(\"REQUEST_METHOD\", \"\")).strip()\n\n\nclass OpenTelemetryMiddleware:\n    \"\"\"The WSGI application middleware.\n\n    This class is a PEP 3333 conforming WSGI middleware that starts and\n    annotates spans for any requests it is invoked with.\n\n    Args:\n        wsgi: The WSGI application callable to forward requests to.\n        request_hook: Optional callback which is called with the server span and WSGI\n                      environ object for every incoming request.\n        response_hook: Optional callback which is called with the server span,\n                       WSGI environ, status_code and response_headers for every\n                       incoming request.\n        tracer_provider: Optional tracer provider to use. If omitted the current\n                         globally configured one is used.\n    \"\"\"\n\n    def __init__(\n        self, wsgi, request_hook=None, response_hook=None, tracer_provider=None\n    ):\n        self.wsgi = wsgi\n        self.tracer = trace.get_tracer(__name__, __version__, tracer_provider)\n        self.request_hook = request_hook\n        self.response_hook = response_hook\n\n    @staticmethod\n    def _create_start_response(span, start_response, response_hook):\n        @functools.wraps(start_response)\n        def _start_response(status, response_headers, *args, **kwargs):\n            add_response_attributes(span, status, response_headers)\n            if response_hook:\n                response_hook(status, response_headers)\n            return start_response(status, response_headers, *args, **kwargs)\n\n        return _start_response\n\n    def __call__(self, environ, start_response):\n        \"\"\"The WSGI application\n\n        Args:\n            environ: A WSGI environment.\n            start_response: The WSGI start_response callable.\n        \"\"\"\n\n        token = context.attach(extract(environ, getter=wsgi_getter))\n\n        span = self.tracer.start_span(\n            get_default_span_name(environ),\n            kind=trace.SpanKind.SERVER,\n            attributes=collect_request_attributes(environ),\n        )\n\n        if self.request_hook:\n            self.request_hook(span, environ)\n\n        response_hook = self.response_hook\n        if response_hook:\n            response_hook = functools.partial(response_hook, span, environ)\n\n        try:\n            with trace.use_span(span):\n                start_response = self._create_start_response(\n                    span, start_response, response_hook\n                )\n                iterable = self.wsgi(environ, start_response)\n                return _end_span_after_iterating(\n                    iterable, span, self.tracer, token\n                )\n        except Exception as ex:\n            if span.is_recording():\n                span.set_status(Status(StatusCode.ERROR, str(ex)))\n            span.end()\n            context.detach(token)\n            raise\n\n\n# Put this in a subfunction to not delay the call to the wrapped\n# WSGI application (instrumentation should change the application\n# behavior as little as possible).\ndef _end_span_after_iterating(iterable, span, tracer, token):\n    try:\n        with trace.use_span(span):\n            for yielded in iterable:\n                yield yielded\n    finally:\n        close = getattr(iterable, \"close\", None)\n        if close:\n            close()\n        span.end()\n        context.detach(token)\n\n\n# TODO: inherit from opentelemetry.instrumentation.propagators.Setter\n\n\nclass ResponsePropagationSetter:\n    def set(self, carrier, key, value):  # pylint: disable=no-self-use\n        carrier.append((key, value))\n\n\ndefault_response_propagation_setter = ResponsePropagationSetter()\n", "path": "instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__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\"\"\"\nThis library provides a WSGI middleware that can be used on any WSGI framework\n(such as Django / Flask / Web.py) to track requests timing through OpenTelemetry.\n\nUsage (Flask)\n-------------\n\n.. code-block:: python\n\n    from flask import Flask\n    from opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware\n\n    app = Flask(__name__)\n    app.wsgi_app = OpenTelemetryMiddleware(app.wsgi_app)\n\n    @app.route(\"/\")\n    def hello():\n        return \"Hello!\"\n\n    if __name__ == \"__main__\":\n        app.run(debug=True)\n\n\nUsage (Django)\n--------------\n\nModify the application's ``wsgi.py`` file as shown below.\n\n.. code-block:: python\n\n    import os\n    from opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware\n    from django.core.wsgi import get_wsgi_application\n\n    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')\n\n    application = get_wsgi_application()\n    application = OpenTelemetryMiddleware(application)\n\nUsage (Web.py)\n--------------\n\n.. code-block:: python\n\n    import web\n    from opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware\n    from cheroot import wsgi\n\n    urls = ('/', 'index')\n\n\n    class index:\n\n        def GET(self):\n            return \"Hello, world!\"\n\n\n    if __name__ == \"__main__\":\n        app = web.application(urls, globals())\n        func = app.wsgifunc()\n\n        func = OpenTelemetryMiddleware(func)\n\n        server = wsgi.WSGIServer(\n            (\"localhost\", 5100), func, server_name=\"localhost\"\n        )\n        server.start()\n\nAPI\n---\n\"\"\"\n\nimport functools\nimport typing\nimport wsgiref.util as wsgiref_util\n\nfrom opentelemetry import context, trace\nfrom opentelemetry.instrumentation.utils import http_status_to_status_code\nfrom opentelemetry.instrumentation.wsgi.version import __version__\nfrom opentelemetry.propagate import extract\nfrom opentelemetry.propagators.textmap import Getter\nfrom opentelemetry.semconv.trace import SpanAttributes\nfrom opentelemetry.trace.status import Status, StatusCode\nfrom opentelemetry.util.http import remove_url_credentials\n\n_HTTP_VERSION_PREFIX = \"HTTP/\"\n_CARRIER_KEY_PREFIX = \"HTTP_\"\n_CARRIER_KEY_PREFIX_LEN = len(_CARRIER_KEY_PREFIX)\n\n\nclass WSGIGetter(Getter):\n    def get(\n        self, carrier: dict, key: str\n    ) -> typing.Optional[typing.List[str]]:\n        \"\"\"Getter implementation to retrieve a HTTP header value from the\n             PEP3333-conforming WSGI environ\n\n        Args:\n             carrier: WSGI environ object\n             key: header name in environ object\n         Returns:\n             A list with a single string with the header value if it exists,\n             else None.\n        \"\"\"\n        environ_key = \"HTTP_\" + key.upper().replace(\"-\", \"_\")\n        value = carrier.get(environ_key)\n        if value is not None:\n            return [value]\n        return None\n\n    def keys(self, carrier):\n        return [\n            key[_CARRIER_KEY_PREFIX_LEN:].lower().replace(\"_\", \"-\")\n            for key in carrier\n            if key.startswith(_CARRIER_KEY_PREFIX)\n        ]\n\n\nwsgi_getter = WSGIGetter()\n\n\ndef setifnotnone(dic, key, value):\n    if value is not None:\n        dic[key] = value\n\n\ndef collect_request_attributes(environ):\n    \"\"\"Collects HTTP request attributes from the PEP3333-conforming\n    WSGI environ and returns a dictionary to be used as span creation attributes.\"\"\"\n\n    result = {\n        SpanAttributes.HTTP_METHOD: environ.get(\"REQUEST_METHOD\"),\n        SpanAttributes.HTTP_SERVER_NAME: environ.get(\"SERVER_NAME\"),\n        SpanAttributes.HTTP_SCHEME: environ.get(\"wsgi.url_scheme\"),\n    }\n\n    host_port = environ.get(\"SERVER_PORT\")\n    if host_port is not None and not host_port == \"\":\n        result.update({SpanAttributes.NET_HOST_PORT: int(host_port)})\n\n    setifnotnone(result, SpanAttributes.HTTP_HOST, environ.get(\"HTTP_HOST\"))\n    target = environ.get(\"RAW_URI\")\n    if target is None:  # Note: `\"\" or None is None`\n        target = environ.get(\"REQUEST_URI\")\n    if target is not None:\n        result[SpanAttributes.HTTP_TARGET] = target\n    else:\n        result[SpanAttributes.HTTP_URL] = remove_url_credentials(\n            wsgiref_util.request_uri(environ)\n        )\n\n    remote_addr = environ.get(\"REMOTE_ADDR\")\n    if remote_addr:\n        result[SpanAttributes.NET_PEER_IP] = remote_addr\n    remote_host = environ.get(\"REMOTE_HOST\")\n    if remote_host and remote_host != remote_addr:\n        result[SpanAttributes.NET_PEER_NAME] = remote_host\n\n    user_agent = environ.get(\"HTTP_USER_AGENT\")\n    if user_agent is not None and len(user_agent) > 0:\n        result[SpanAttributes.HTTP_USER_AGENT] = user_agent\n\n    setifnotnone(\n        result, SpanAttributes.NET_PEER_PORT, environ.get(\"REMOTE_PORT\")\n    )\n    flavor = environ.get(\"SERVER_PROTOCOL\", \"\")\n    if flavor.upper().startswith(_HTTP_VERSION_PREFIX):\n        flavor = flavor[len(_HTTP_VERSION_PREFIX) :]\n    if flavor:\n        result[SpanAttributes.HTTP_FLAVOR] = flavor\n\n    return result\n\n\ndef add_response_attributes(\n    span, start_response_status, response_headers\n):  # pylint: disable=unused-argument\n    \"\"\"Adds HTTP response attributes to span using the arguments\n    passed to a PEP3333-conforming start_response callable.\"\"\"\n    if not span.is_recording():\n        return\n    status_code, _ = start_response_status.split(\" \", 1)\n\n    try:\n        status_code = int(status_code)\n    except ValueError:\n        span.set_status(\n            Status(\n                StatusCode.ERROR,\n                \"Non-integer HTTP status: \" + repr(status_code),\n            )\n        )\n    else:\n        span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, status_code)\n        span.set_status(Status(http_status_to_status_code(status_code)))\n\n\ndef get_default_span_name(environ):\n    \"\"\"Default implementation for name_callback, returns HTTP {METHOD_NAME}.\"\"\"\n    return \"HTTP {}\".format(environ.get(\"REQUEST_METHOD\", \"\")).strip()\n\n\nclass OpenTelemetryMiddleware:\n    \"\"\"The WSGI application middleware.\n\n    This class is a PEP 3333 conforming WSGI middleware that starts and\n    annotates spans for any requests it is invoked with.\n\n    Args:\n        wsgi: The WSGI application callable to forward requests to.\n        request_hook: Optional callback which is called with the server span and WSGI\n                      environ object for every incoming request.\n        response_hook: Optional callback which is called with the server span,\n                       WSGI environ, status_code and response_headers for every\n                       incoming request.\n        tracer_provider: Optional tracer provider to use. If omitted the current\n                         globally configured one is used.\n    \"\"\"\n\n    def __init__(\n        self, wsgi, request_hook=None, response_hook=None, tracer_provider=None\n    ):\n        self.wsgi = wsgi\n        self.tracer = trace.get_tracer(__name__, __version__, tracer_provider)\n        self.request_hook = request_hook\n        self.response_hook = response_hook\n\n    @staticmethod\n    def _create_start_response(span, start_response, response_hook):\n        @functools.wraps(start_response)\n        def _start_response(status, response_headers, *args, **kwargs):\n            add_response_attributes(span, status, response_headers)\n            if response_hook:\n                response_hook(status, response_headers)\n            return start_response(status, response_headers, *args, **kwargs)\n\n        return _start_response\n\n    def __call__(self, environ, start_response):\n        \"\"\"The WSGI application\n\n        Args:\n            environ: A WSGI environment.\n            start_response: The WSGI start_response callable.\n        \"\"\"\n\n        token = context.attach(extract(environ, getter=wsgi_getter))\n\n        span = self.tracer.start_span(\n            get_default_span_name(environ),\n            kind=trace.SpanKind.SERVER,\n            attributes=collect_request_attributes(environ),\n        )\n\n        if self.request_hook:\n            self.request_hook(span, environ)\n\n        response_hook = self.response_hook\n        if response_hook:\n            response_hook = functools.partial(response_hook, span, environ)\n\n        try:\n            with trace.use_span(span):\n                start_response = self._create_start_response(\n                    span, start_response, response_hook\n                )\n                iterable = self.wsgi(environ, start_response)\n                return _end_span_after_iterating(\n                    iterable, span, self.tracer, token\n                )\n        except Exception as ex:\n            if span.is_recording():\n                span.set_status(Status(StatusCode.ERROR, str(ex)))\n            span.end()\n            context.detach(token)\n            raise\n\n\n# Put this in a subfunction to not delay the call to the wrapped\n# WSGI application (instrumentation should change the application\n# behavior as little as possible).\ndef _end_span_after_iterating(iterable, span, tracer, token):\n    try:\n        with trace.use_span(span):\n            for yielded in iterable:\n                yield yielded\n    finally:\n        close = getattr(iterable, \"close\", None)\n        if close:\n            close()\n        span.end()\n        context.detach(token)\n\n\n# TODO: inherit from opentelemetry.instrumentation.propagators.Setter\n\n\nclass ResponsePropagationSetter:\n    def set(self, carrier, key, value):  # pylint: disable=no-self-use\n        carrier.append((key, value))\n\n\ndefault_response_propagation_setter = ResponsePropagationSetter()\n", "path": "instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py"}]}