We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[WSGI] Replace span name callback with request and response hooks 
WSGI instrumentation accepts a span name callback which should be replaced with more generic request/response callbacks (hooks). 

Details: https://github.com/open-telemetry/opentelemetry-python-contrib/issues/408

--- 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.trace.status import Status, StatusCode
67 
68 _HTTP_VERSION_PREFIX = "HTTP/"
69 _CARRIER_KEY_PREFIX = "HTTP_"
70 _CARRIER_KEY_PREFIX_LEN = len(_CARRIER_KEY_PREFIX)
71 
72 
73 class WSGIGetter(Getter):
74     def get(
75         self, carrier: dict, key: str
76     ) -> typing.Optional[typing.List[str]]:
77         """Getter implementation to retrieve a HTTP header value from the
78             PEP3333-conforming WSGI environ
79 
80        Args:
81             carrier: WSGI environ object
82             key: header name in environ object
83         Returns:
84             A list with a single string with the header value if it exists,
85             else None.
86         """
87         environ_key = "HTTP_" + key.upper().replace("-", "_")
88         value = carrier.get(environ_key)
89         if value is not None:
90             return [value]
91         return None
92 
93     def keys(self, carrier):
94         return [
95             key[_CARRIER_KEY_PREFIX_LEN:].lower().replace("_", "-")
96             for key in carrier
97             if key.startswith(_CARRIER_KEY_PREFIX)
98         ]
99 
100 
101 wsgi_getter = WSGIGetter()
102 
103 
104 def setifnotnone(dic, key, value):
105     if value is not None:
106         dic[key] = value
107 
108 
109 def collect_request_attributes(environ):
110     """Collects HTTP request attributes from the PEP3333-conforming
111     WSGI environ and returns a dictionary to be used as span creation attributes."""
112 
113     result = {
114         "http.method": environ.get("REQUEST_METHOD"),
115         "http.server_name": environ.get("SERVER_NAME"),
116         "http.scheme": environ.get("wsgi.url_scheme"),
117     }
118 
119     host_port = environ.get("SERVER_PORT")
120     if host_port is not None:
121         result.update({"net.host.port": int(host_port)})
122 
123     setifnotnone(result, "http.host", environ.get("HTTP_HOST"))
124     target = environ.get("RAW_URI")
125     if target is None:  # Note: `"" or None is None`
126         target = environ.get("REQUEST_URI")
127     if target is not None:
128         result["http.target"] = target
129     else:
130         result["http.url"] = wsgiref_util.request_uri(environ)
131 
132     remote_addr = environ.get("REMOTE_ADDR")
133     if remote_addr:
134         result["net.peer.ip"] = remote_addr
135     remote_host = environ.get("REMOTE_HOST")
136     if remote_host and remote_host != remote_addr:
137         result["net.peer.name"] = remote_host
138 
139     user_agent = environ.get("HTTP_USER_AGENT")
140     if user_agent is not None and len(user_agent) > 0:
141         result["http.user_agent"] = user_agent
142 
143     setifnotnone(result, "net.peer.port", environ.get("REMOTE_PORT"))
144     flavor = environ.get("SERVER_PROTOCOL", "")
145     if flavor.upper().startswith(_HTTP_VERSION_PREFIX):
146         flavor = flavor[len(_HTTP_VERSION_PREFIX) :]
147     if flavor:
148         result["http.flavor"] = flavor
149 
150     return result
151 
152 
153 def add_response_attributes(
154     span, start_response_status, response_headers
155 ):  # pylint: disable=unused-argument
156     """Adds HTTP response attributes to span using the arguments
157     passed to a PEP3333-conforming start_response callable."""
158     if not span.is_recording():
159         return
160     status_code, _ = start_response_status.split(" ", 1)
161 
162     try:
163         status_code = int(status_code)
164     except ValueError:
165         span.set_status(
166             Status(
167                 StatusCode.ERROR,
168                 "Non-integer HTTP status: " + repr(status_code),
169             )
170         )
171     else:
172         span.set_attribute("http.status_code", status_code)
173         span.set_status(Status(http_status_to_status_code(status_code)))
174 
175 
176 def get_default_span_name(environ):
177     """Default implementation for name_callback, returns HTTP {METHOD_NAME}."""
178     return "HTTP {}".format(environ.get("REQUEST_METHOD", "")).strip()
179 
180 
181 class OpenTelemetryMiddleware:
182     """The WSGI application middleware.
183 
184     This class is a PEP 3333 conforming WSGI middleware that starts and
185     annotates spans for any requests it is invoked with.
186 
187     Args:
188         wsgi: The WSGI application callable to forward requests to.
189         name_callback: Callback which calculates a generic span name for an
190                        incoming HTTP request based on the PEP3333 WSGI environ.
191                        Optional: Defaults to get_default_span_name.
192     """
193 
194     def __init__(self, wsgi, name_callback=get_default_span_name):
195         self.wsgi = wsgi
196         self.tracer = trace.get_tracer(__name__, __version__)
197         self.name_callback = name_callback
198 
199     @staticmethod
200     def _create_start_response(span, start_response):
201         @functools.wraps(start_response)
202         def _start_response(status, response_headers, *args, **kwargs):
203             add_response_attributes(span, status, response_headers)
204             return start_response(status, response_headers, *args, **kwargs)
205 
206         return _start_response
207 
208     def __call__(self, environ, start_response):
209         """The WSGI application
210 
211         Args:
212             environ: A WSGI environment.
213             start_response: The WSGI start_response callable.
214         """
215 
216         token = context.attach(extract(environ, getter=wsgi_getter))
217         span_name = self.name_callback(environ)
218 
219         span = self.tracer.start_span(
220             span_name,
221             kind=trace.SpanKind.SERVER,
222             attributes=collect_request_attributes(environ),
223         )
224 
225         try:
226             with trace.use_span(span):
227                 start_response = self._create_start_response(
228                     span, start_response
229                 )
230                 iterable = self.wsgi(environ, start_response)
231                 return _end_span_after_iterating(
232                     iterable, span, self.tracer, token
233                 )
234         except Exception as ex:
235             if span.is_recording():
236                 span.set_status(Status(StatusCode.ERROR, str(ex)))
237             span.end()
238             context.detach(token)
239             raise
240 
241 
242 # Put this in a subfunction to not delay the call to the wrapped
243 # WSGI application (instrumentation should change the application
244 # behavior as little as possible).
245 def _end_span_after_iterating(iterable, span, tracer, token):
246     try:
247         with trace.use_span(span):
248             for yielded in iterable:
249                 yield yielded
250     finally:
251         close = getattr(iterable, "close", None)
252         if close:
253             close()
254         span.end()
255         context.detach(token)
256
```
--- 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
@@ -186,21 +186,26 @@
 
     Args:
         wsgi: The WSGI application callable to forward requests to.
-        name_callback: Callback which calculates a generic span name for an
-                       incoming HTTP request based on the PEP3333 WSGI environ.
-                       Optional: Defaults to get_default_span_name.
+        request_hook: Optional callback which is called with the server span and WSGI
+                      environ object for every incoming request.
+        response_hook: Optional callback which is called with the server span,
+                       WSGI environ, status_code and response_headers for every
+                       incoming request.
     """
 
-    def __init__(self, wsgi, name_callback=get_default_span_name):
+    def __init__(self, wsgi, request_hook=None, response_hook=None):
         self.wsgi = wsgi
         self.tracer = trace.get_tracer(__name__, __version__)
-        self.name_callback = name_callback
+        self.request_hook = request_hook
+        self.response_hook = response_hook
 
     @staticmethod
-    def _create_start_response(span, start_response):
+    def _create_start_response(span, start_response, response_hook):
         @functools.wraps(start_response)
         def _start_response(status, response_headers, *args, **kwargs):
             add_response_attributes(span, status, response_headers)
+            if response_hook:
+                response_hook(status, response_headers)
             return start_response(status, response_headers, *args, **kwargs)
 
         return _start_response
@@ -214,18 +219,24 @@
         """
 
         token = context.attach(extract(environ, getter=wsgi_getter))
-        span_name = self.name_callback(environ)
 
         span = self.tracer.start_span(
-            span_name,
+            get_default_span_name(environ),
             kind=trace.SpanKind.SERVER,
             attributes=collect_request_attributes(environ),
         )
 
+        if self.request_hook:
+            self.request_hook(span, environ)
+
+        response_hook = self.response_hook
+        if response_hook:
+            response_hook = functools.partial(response_hook, span, environ)
+
         try:
             with trace.use_span(span):
                 start_response = self._create_start_response(
-                    span, start_response
+                    span, start_response, response_hook
                 )
                 iterable = self.wsgi(environ, start_response)
                 return _end_span_after_iterating(

{"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@@ -186,21 +186,26 @@\n \n     Args:\n         wsgi: The WSGI application callable to forward requests to.\n-        name_callback: Callback which calculates a generic span name for an\n-                       incoming HTTP request based on the PEP3333 WSGI environ.\n-                       Optional: Defaults to get_default_span_name.\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     \"\"\"\n \n-    def __init__(self, wsgi, name_callback=get_default_span_name):\n+    def __init__(self, wsgi, request_hook=None, response_hook=None):\n         self.wsgi = wsgi\n         self.tracer = trace.get_tracer(__name__, __version__)\n-        self.name_callback = name_callback\n+        self.request_hook = request_hook\n+        self.response_hook = response_hook\n \n     @staticmethod\n-    def _create_start_response(span, start_response):\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@@ -214,18 +219,24 @@\n         \"\"\"\n \n         token = context.attach(extract(environ, getter=wsgi_getter))\n-        span_name = self.name_callback(environ)\n \n         span = self.tracer.start_span(\n-            span_name,\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\n+                    span, start_response, response_hook\n                 )\n                 iterable = self.wsgi(environ, start_response)\n                 return _end_span_after_iterating(\n", "issue": "[WSGI] Replace span name callback with request and response hooks \nWSGI instrumentation accepts a span name callback which should be replaced with more generic request/response callbacks (hooks). \r\n\r\nDetails: https://github.com/open-telemetry/opentelemetry-python-contrib/issues/408\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.trace.status import Status, StatusCode\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        \"http.method\": environ.get(\"REQUEST_METHOD\"),\n        \"http.server_name\": environ.get(\"SERVER_NAME\"),\n        \"http.scheme\": environ.get(\"wsgi.url_scheme\"),\n    }\n\n    host_port = environ.get(\"SERVER_PORT\")\n    if host_port is not None:\n        result.update({\"net.host.port\": int(host_port)})\n\n    setifnotnone(result, \"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[\"http.target\"] = target\n    else:\n        result[\"http.url\"] = wsgiref_util.request_uri(environ)\n\n    remote_addr = environ.get(\"REMOTE_ADDR\")\n    if remote_addr:\n        result[\"net.peer.ip\"] = remote_addr\n    remote_host = environ.get(\"REMOTE_HOST\")\n    if remote_host and remote_host != remote_addr:\n        result[\"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[\"http.user_agent\"] = user_agent\n\n    setifnotnone(result, \"net.peer.port\", environ.get(\"REMOTE_PORT\"))\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[\"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(\"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        name_callback: Callback which calculates a generic span name for an\n                       incoming HTTP request based on the PEP3333 WSGI environ.\n                       Optional: Defaults to get_default_span_name.\n    \"\"\"\n\n    def __init__(self, wsgi, name_callback=get_default_span_name):\n        self.wsgi = wsgi\n        self.tracer = trace.get_tracer(__name__, __version__)\n        self.name_callback = name_callback\n\n    @staticmethod\n    def _create_start_response(span, start_response):\n        @functools.wraps(start_response)\n        def _start_response(status, response_headers, *args, **kwargs):\n            add_response_attributes(span, 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        span_name = self.name_callback(environ)\n\n        span = self.tracer.start_span(\n            span_name,\n            kind=trace.SpanKind.SERVER,\n            attributes=collect_request_attributes(environ),\n        )\n\n        try:\n            with trace.use_span(span):\n                start_response = self._create_start_response(\n                    span, start_response\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", "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) 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.trace.status import Status, StatusCode\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        \"http.method\": environ.get(\"REQUEST_METHOD\"),\n        \"http.server_name\": environ.get(\"SERVER_NAME\"),\n        \"http.scheme\": environ.get(\"wsgi.url_scheme\"),\n    }\n\n    host_port = environ.get(\"SERVER_PORT\")\n    if host_port is not None:\n        result.update({\"net.host.port\": int(host_port)})\n\n    setifnotnone(result, \"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[\"http.target\"] = target\n    else:\n        result[\"http.url\"] = wsgiref_util.request_uri(environ)\n\n    remote_addr = environ.get(\"REMOTE_ADDR\")\n    if remote_addr:\n        result[\"net.peer.ip\"] = remote_addr\n    remote_host = environ.get(\"REMOTE_HOST\")\n    if remote_host and remote_host != remote_addr:\n        result[\"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[\"http.user_agent\"] = user_agent\n\n    setifnotnone(result, \"net.peer.port\", environ.get(\"REMOTE_PORT\"))\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[\"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(\"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    \"\"\"\n\n    def __init__(self, wsgi, request_hook=None, response_hook=None):\n        self.wsgi = wsgi\n        self.tracer = trace.get_tracer(__name__, __version__)\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", "path": "instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py"}]}