We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Bug: Prometheus client causes immediate websocket disconnect 
### Description

When using the Prometheus integration, websockets always disconnect immediately after opening the connection.

I've done a bit of digging, and the error arises in `PrometheusMiddleware._get_default_labels` [here](https://github.com/litestar-org/litestar/blob/main/litestar/contrib/prometheus/middleware.py#L107-L122), where `request.method` raises an attribute error for websockets. One possible fix that I've tested locally is to change it to:
```python
"method": (
    request.method
    if request.scope["type"] == "http"
    else request.scope["type"]
),
```

I'd be happy to open a PR for this change or some other fix if someone has a better idea.

### URL to code causing the issue

_No response_

### MCVE

```python
from litestar import Litestar, websocket_listener
from litestar.testing import TestClient
from litestar.contrib.prometheus import PrometheusConfig, PrometheusController

config = PrometheusConfig()

@websocket_listener("/")
def websocket_handler(data: str):
    return f"Hello, {data}!"

app_with_prometheus = Litestar(
    route_handlers=[websocket_handler, PrometheusController],
    middleware=[config.middleware]
)

app_without_prometheus = Litestar(route_handlers=[websocket_handler])

def test_app(app_to_test):
    with TestClient(app=app_to_test) as test_client:
        with test_client.websocket_connect("/") as socket:
            socket.send("World")
            response = socket.receive()
            print(response)

test_app(app_without_prometheus)
test_app(app_with_prometheus)
```


### Steps to reproduce

```bash
1. Run the MCVE
2. Observe the traceback
```


### Screenshots

_No response_

### Logs

```bash
Traceback (most recent call last):
  File "/Users/erik/test.py", line 27, in <module>
    test_app(app_with_prometheus)
  File "/Users/erik/test.py", line 20, in test_app
    with test_client.websocket_connect("/") as socket:
  File "/Users/erik/Library/Caches/pypoetry/virtualenvs/*****-ocilal_E-py3.11/lib/python3.11/site-packages/litestar/testing/websocket_test_session.py", line 52, in __enter__
    message = self.receive(timeout=self.client.timeout.read)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/erik/Library/Caches/pypoetry/virtualenvs/*****-ocilal_E-py3.11/lib/python3.11/site-packages/litestar/testing/websocket_test_session.py", line 195, in receive
    raise WebSocketDisconnect(
litestar.exceptions.websocket_exceptions.WebSocketDisconnect: KeyError('method')
```


### Litestar Version

2.0.0b2

### Platform

- [ ] Linux
- [X] Mac
- [ ] Windows
- [ ] Other (Please specify in the description above)

<!-- POLAR PLEDGE BADGE START -->
## Funding
* You can sponsor this specific effort via a [Polar.sh](https://polar.sh) pledge below
* We receive the pledge once the issue is completed & verified

<a href="https://polar.sh/litestar-org/litestar/issues/1934">
<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://polar.sh/api/github/litestar-org/litestar/issues/1934/pledge.svg?darkmode=1">
  <img alt="Fund with Polar" src="https://polar.sh/api/github/litestar-org/litestar/issues/1934/pledge.svg">
</picture>
</a>
<!-- POLAR PLEDGE BADGE END -->

Bug: Prometheus client causes immediate websocket disconnect 
### Description

When using the Prometheus integration, websockets always disconnect immediately after opening the connection.

I've done a bit of digging, and the error arises in `PrometheusMiddleware._get_default_labels` [here](https://github.com/litestar-org/litestar/blob/main/litestar/contrib/prometheus/middleware.py#L107-L122), where `request.method` raises an attribute error for websockets. One possible fix that I've tested locally is to change it to:
```python
"method": (
    request.method
    if request.scope["type"] == "http"
    else request.scope["type"]
),
```

I'd be happy to open a PR for this change or some other fix if someone has a better idea.

### URL to code causing the issue

_No response_

### MCVE

```python
from litestar import Litestar, websocket_listener
from litestar.testing import TestClient
from litestar.contrib.prometheus import PrometheusConfig, PrometheusController

config = PrometheusConfig()

@websocket_listener("/")
def websocket_handler(data: str):
    return f"Hello, {data}!"

app_with_prometheus = Litestar(
    route_handlers=[websocket_handler, PrometheusController],
    middleware=[config.middleware]
)

app_without_prometheus = Litestar(route_handlers=[websocket_handler])

def test_app(app_to_test):
    with TestClient(app=app_to_test) as test_client:
        with test_client.websocket_connect("/") as socket:
            socket.send("World")
            response = socket.receive()
            print(response)

test_app(app_without_prometheus)
test_app(app_with_prometheus)
```


### Steps to reproduce

```bash
1. Run the MCVE
2. Observe the traceback
```


### Screenshots

_No response_

### Logs

```bash
Traceback (most recent call last):
  File "/Users/erik/test.py", line 27, in <module>
    test_app(app_with_prometheus)
  File "/Users/erik/test.py", line 20, in test_app
    with test_client.websocket_connect("/") as socket:
  File "/Users/erik/Library/Caches/pypoetry/virtualenvs/*****-ocilal_E-py3.11/lib/python3.11/site-packages/litestar/testing/websocket_test_session.py", line 52, in __enter__
    message = self.receive(timeout=self.client.timeout.read)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/erik/Library/Caches/pypoetry/virtualenvs/*****-ocilal_E-py3.11/lib/python3.11/site-packages/litestar/testing/websocket_test_session.py", line 195, in receive
    raise WebSocketDisconnect(
litestar.exceptions.websocket_exceptions.WebSocketDisconnect: KeyError('method')
```


### Litestar Version

2.0.0b2

### Platform

- [ ] Linux
- [X] Mac
- [ ] Windows
- [ ] Other (Please specify in the description above)

<!-- POLAR PLEDGE BADGE START -->
## Funding
* You can sponsor this specific effort via a [Polar.sh](https://polar.sh) pledge below
* We receive the pledge once the issue is completed & verified

<a href="https://polar.sh/litestar-org/litestar/issues/1934">
<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://polar.sh/api/github/litestar-org/litestar/issues/1934/pledge.svg?darkmode=1">
  <img alt="Fund with Polar" src="https://polar.sh/api/github/litestar-org/litestar/issues/1934/pledge.svg">
</picture>
</a>
<!-- POLAR PLEDGE BADGE END -->


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `litestar/contrib/prometheus/middleware.py`
Content:
```
1 from __future__ import annotations
2 
3 import time
4 from functools import wraps
5 from typing import TYPE_CHECKING, Any, Callable, ClassVar, cast
6 
7 from litestar.connection.request import Request
8 from litestar.exceptions import MissingDependencyException
9 from litestar.middleware.base import AbstractMiddleware
10 
11 __all__ = ("PrometheusMiddleware",)
12 
13 from litestar.status_codes import HTTP_500_INTERNAL_SERVER_ERROR
14 
15 try:
16     import prometheus_client  # noqa: F401
17 except ImportError as e:
18     raise MissingDependencyException("prometheus_client") from e
19 
20 from prometheus_client import Counter, Gauge, Histogram
21 
22 if TYPE_CHECKING:
23     from prometheus_client.metrics import MetricWrapperBase
24 
25     from litestar.contrib.prometheus import PrometheusConfig
26     from litestar.types import ASGIApp, Message, Receive, Scope, Send
27 
28 
29 class PrometheusMiddleware(AbstractMiddleware):
30     """Prometheus Middleware."""
31 
32     _metrics: ClassVar[dict[str, MetricWrapperBase]] = {}
33 
34     def __init__(self, app: ASGIApp, config: PrometheusConfig) -> None:
35         """Middleware that adds Prometheus instrumentation to the application.
36 
37         Args:
38             app: The ``next`` ASGI app to call.
39             config: An instance of :class:`PrometheusConfig <.contrib.prometheus.PrometheusConfig>`
40         """
41         super().__init__(app=app, scopes=config.scopes, exclude=config.exclude, exclude_opt_key=config.exclude_opt_key)
42         self._config = config
43         self._kwargs: dict[str, Any] = {}
44 
45         if self._config.buckets is not None:
46             self._kwargs["buckets"] = self._config.buckets
47 
48     def request_count(self, labels: dict[str, str | int | float]) -> Counter:
49         metric_name = f"{self._config.prefix}_requests_total"
50 
51         if metric_name not in PrometheusMiddleware._metrics:
52             PrometheusMiddleware._metrics[metric_name] = Counter(
53                 name=metric_name,
54                 documentation="Total requests",
55                 labelnames=[*labels.keys()],
56             )
57 
58         return cast("Counter", PrometheusMiddleware._metrics[metric_name])
59 
60     def request_time(self, labels: dict[str, str | int | float]) -> Histogram:
61         metric_name = f"{self._config.prefix}_request_duration_seconds"
62 
63         if metric_name not in PrometheusMiddleware._metrics:
64             PrometheusMiddleware._metrics[metric_name] = Histogram(
65                 name=metric_name,
66                 documentation="Request duration, in seconds",
67                 labelnames=[*labels.keys()],
68                 **self._kwargs,
69             )
70         return cast("Histogram", PrometheusMiddleware._metrics[metric_name])
71 
72     def requests_in_progress(self, labels: dict[str, str | int | float]) -> Gauge:
73         metric_name = f"{self._config.prefix}_requests_in_progress"
74 
75         if metric_name not in PrometheusMiddleware._metrics:
76             PrometheusMiddleware._metrics[metric_name] = Gauge(
77                 name=metric_name,
78                 documentation="Total requests currently in progress",
79                 labelnames=[*labels.keys()],
80                 multiprocess_mode="livesum",
81             )
82         return cast("Gauge", PrometheusMiddleware._metrics[metric_name])
83 
84     def requests_error_count(self, labels: dict[str, str | int | float]) -> Counter:
85         metric_name = f"{self._config.prefix}_requests_error_total"
86 
87         if metric_name not in PrometheusMiddleware._metrics:
88             PrometheusMiddleware._metrics[metric_name] = Counter(
89                 name=metric_name,
90                 documentation="Total errors in requests",
91                 labelnames=[*labels.keys()],
92             )
93         return cast("Counter", PrometheusMiddleware._metrics[metric_name])
94 
95     def _get_extra_labels(self, request: Request[Any, Any, Any]) -> dict[str, str]:
96         """Get extra labels provided by the config and if they are callable, parse them.
97 
98         Args:
99         request: The request object.
100 
101         Returns:
102         A dictionary of extra labels.
103         """
104 
105         return {k: str(v(request) if callable(v) else v) for k, v in (self._config.labels or {}).items()}
106 
107     def _get_default_labels(self, request: Request[Any, Any, Any]) -> dict[str, str | int | float]:
108         """Get default label values from the request.
109 
110         Args:
111             request: The request object.
112 
113         Returns:
114             A dictionary of default labels.
115         """
116 
117         return {
118             "method": request.method,
119             "path": request.url.path,
120             "status_code": 200,
121             "app_name": self._config.app_name,
122         }
123 
124     async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
125         """ASGI callable.
126 
127         Args:
128             scope: The ASGI connection scope.
129             receive: The ASGI receive function.
130             send: The ASGI send function.
131 
132         Returns:
133             None
134         """
135 
136         request = Request[Any, Any, Any](scope, receive)
137 
138         if self._config.excluded_http_methods and request.method in self._config.excluded_http_methods:
139             await self.app(scope, receive, send)
140             return
141 
142         labels = {**self._get_default_labels(request), **self._get_extra_labels(request)}
143 
144         request_span = {"start_time": time.perf_counter(), "end_time": 0, "duration": 0, "status_code": 200}
145 
146         wrapped_send = self._get_wrapped_send(send, request_span)
147 
148         self.requests_in_progress(labels).labels(*labels.values()).inc()
149 
150         try:
151             await self.app(scope, receive, wrapped_send)
152         finally:
153             extra: dict[str, Any] = {}
154             if self._config.exemplars:
155                 extra["exemplar"] = self._config.exemplars(request)
156 
157             self.requests_in_progress(labels).labels(*labels.values()).dec()
158 
159             labels["status_code"] = request_span["status_code"]
160             label_values = [*labels.values()]
161 
162             if request_span["status_code"] >= HTTP_500_INTERNAL_SERVER_ERROR:
163                 self.requests_error_count(labels).labels(*label_values).inc(**extra)
164 
165             self.request_count(labels).labels(*label_values).inc(**extra)
166             self.request_time(labels).labels(*label_values).observe(request_span["duration"], **extra)
167 
168     def _get_wrapped_send(self, send: Send, request_span: dict[str, float]) -> Callable:
169         @wraps(send)
170         async def wrapped_send(message: Message) -> None:
171             if message["type"] == "http.response.start":
172                 request_span["status_code"] = message["status"]
173 
174             if message["type"] == "http.response.body":
175                 end = time.perf_counter()
176                 request_span["duration"] = end - request_span["start_time"]
177                 request_span["end_time"] = end
178             await send(message)
179 
180         return wrapped_send
181
```
--- 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/litestar/contrib/prometheus/middleware.py b/litestar/contrib/prometheus/middleware.py
--- a/litestar/contrib/prometheus/middleware.py
+++ b/litestar/contrib/prometheus/middleware.py
@@ -5,6 +5,7 @@
 from typing import TYPE_CHECKING, Any, Callable, ClassVar, cast
 
 from litestar.connection.request import Request
+from litestar.enums import ScopeType
 from litestar.exceptions import MissingDependencyException
 from litestar.middleware.base import AbstractMiddleware
 
@@ -115,7 +116,7 @@
         """
 
         return {
-            "method": request.method,
+            "method": request.method if request.scope["type"] == ScopeType.HTTP else request.scope["type"],
             "path": request.url.path,
             "status_code": 200,
             "app_name": self._config.app_name,

{"golden_diff": "diff --git a/litestar/contrib/prometheus/middleware.py b/litestar/contrib/prometheus/middleware.py\n--- a/litestar/contrib/prometheus/middleware.py\n+++ b/litestar/contrib/prometheus/middleware.py\n@@ -5,6 +5,7 @@\n from typing import TYPE_CHECKING, Any, Callable, ClassVar, cast\n \n from litestar.connection.request import Request\n+from litestar.enums import ScopeType\n from litestar.exceptions import MissingDependencyException\n from litestar.middleware.base import AbstractMiddleware\n \n@@ -115,7 +116,7 @@\n         \"\"\"\n \n         return {\n-            \"method\": request.method,\n+            \"method\": request.method if request.scope[\"type\"] == ScopeType.HTTP else request.scope[\"type\"],\n             \"path\": request.url.path,\n             \"status_code\": 200,\n             \"app_name\": self._config.app_name,\n", "issue": "Bug: Prometheus client causes immediate websocket disconnect \n### Description\n\nWhen using the Prometheus integration, websockets always disconnect immediately after opening the connection.\r\n\r\nI've done a bit of digging, and the error arises in `PrometheusMiddleware._get_default_labels` [here](https://github.com/litestar-org/litestar/blob/main/litestar/contrib/prometheus/middleware.py#L107-L122), where `request.method` raises an attribute error for websockets. One possible fix that I've tested locally is to change it to:\r\n```python\r\n\"method\": (\r\n    request.method\r\n    if request.scope[\"type\"] == \"http\"\r\n    else request.scope[\"type\"]\r\n),\r\n```\r\n\r\nI'd be happy to open a PR for this change or some other fix if someone has a better idea.\n\n### URL to code causing the issue\n\n_No response_\n\n### MCVE\n\n```python\nfrom litestar import Litestar, websocket_listener\r\nfrom litestar.testing import TestClient\r\nfrom litestar.contrib.prometheus import PrometheusConfig, PrometheusController\r\n\r\nconfig = PrometheusConfig()\r\n\r\n@websocket_listener(\"/\")\r\ndef websocket_handler(data: str):\r\n    return f\"Hello, {data}!\"\r\n\r\napp_with_prometheus = Litestar(\r\n    route_handlers=[websocket_handler, PrometheusController],\r\n    middleware=[config.middleware]\r\n)\r\n\r\napp_without_prometheus = Litestar(route_handlers=[websocket_handler])\r\n\r\ndef test_app(app_to_test):\r\n    with TestClient(app=app_to_test) as test_client:\r\n        with test_client.websocket_connect(\"/\") as socket:\r\n            socket.send(\"World\")\r\n            response = socket.receive()\r\n            print(response)\r\n\r\ntest_app(app_without_prometheus)\r\ntest_app(app_with_prometheus)\n```\n\n\n### Steps to reproduce\n\n```bash\n1. Run the MCVE\r\n2. Observe the traceback\n```\n\n\n### Screenshots\n\n_No response_\n\n### Logs\n\n```bash\nTraceback (most recent call last):\r\n  File \"/Users/erik/test.py\", line 27, in <module>\r\n    test_app(app_with_prometheus)\r\n  File \"/Users/erik/test.py\", line 20, in test_app\r\n    with test_client.websocket_connect(\"/\") as socket:\r\n  File \"/Users/erik/Library/Caches/pypoetry/virtualenvs/*****-ocilal_E-py3.11/lib/python3.11/site-packages/litestar/testing/websocket_test_session.py\", line 52, in __enter__\r\n    message = self.receive(timeout=self.client.timeout.read)\r\n              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n  File \"/Users/erik/Library/Caches/pypoetry/virtualenvs/*****-ocilal_E-py3.11/lib/python3.11/site-packages/litestar/testing/websocket_test_session.py\", line 195, in receive\r\n    raise WebSocketDisconnect(\r\nlitestar.exceptions.websocket_exceptions.WebSocketDisconnect: KeyError('method')\n```\n\n\n### Litestar Version\n\n2.0.0b2\n\n### Platform\n\n- [ ] Linux\n- [X] Mac\n- [ ] Windows\n- [ ] Other (Please specify in the description above)\n\n<!-- POLAR PLEDGE BADGE START -->\n## Funding\n* You can sponsor this specific effort via a [Polar.sh](https://polar.sh) pledge below\n* We receive the pledge once the issue is completed & verified\n\n<a href=\"https://polar.sh/litestar-org/litestar/issues/1934\">\n<picture>\n  <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://polar.sh/api/github/litestar-org/litestar/issues/1934/pledge.svg?darkmode=1\">\n  <img alt=\"Fund with Polar\" src=\"https://polar.sh/api/github/litestar-org/litestar/issues/1934/pledge.svg\">\n</picture>\n</a>\n<!-- POLAR PLEDGE BADGE END -->\n\nBug: Prometheus client causes immediate websocket disconnect \n### Description\n\nWhen using the Prometheus integration, websockets always disconnect immediately after opening the connection.\r\n\r\nI've done a bit of digging, and the error arises in `PrometheusMiddleware._get_default_labels` [here](https://github.com/litestar-org/litestar/blob/main/litestar/contrib/prometheus/middleware.py#L107-L122), where `request.method` raises an attribute error for websockets. One possible fix that I've tested locally is to change it to:\r\n```python\r\n\"method\": (\r\n    request.method\r\n    if request.scope[\"type\"] == \"http\"\r\n    else request.scope[\"type\"]\r\n),\r\n```\r\n\r\nI'd be happy to open a PR for this change or some other fix if someone has a better idea.\n\n### URL to code causing the issue\n\n_No response_\n\n### MCVE\n\n```python\nfrom litestar import Litestar, websocket_listener\r\nfrom litestar.testing import TestClient\r\nfrom litestar.contrib.prometheus import PrometheusConfig, PrometheusController\r\n\r\nconfig = PrometheusConfig()\r\n\r\n@websocket_listener(\"/\")\r\ndef websocket_handler(data: str):\r\n    return f\"Hello, {data}!\"\r\n\r\napp_with_prometheus = Litestar(\r\n    route_handlers=[websocket_handler, PrometheusController],\r\n    middleware=[config.middleware]\r\n)\r\n\r\napp_without_prometheus = Litestar(route_handlers=[websocket_handler])\r\n\r\ndef test_app(app_to_test):\r\n    with TestClient(app=app_to_test) as test_client:\r\n        with test_client.websocket_connect(\"/\") as socket:\r\n            socket.send(\"World\")\r\n            response = socket.receive()\r\n            print(response)\r\n\r\ntest_app(app_without_prometheus)\r\ntest_app(app_with_prometheus)\n```\n\n\n### Steps to reproduce\n\n```bash\n1. Run the MCVE\r\n2. Observe the traceback\n```\n\n\n### Screenshots\n\n_No response_\n\n### Logs\n\n```bash\nTraceback (most recent call last):\r\n  File \"/Users/erik/test.py\", line 27, in <module>\r\n    test_app(app_with_prometheus)\r\n  File \"/Users/erik/test.py\", line 20, in test_app\r\n    with test_client.websocket_connect(\"/\") as socket:\r\n  File \"/Users/erik/Library/Caches/pypoetry/virtualenvs/*****-ocilal_E-py3.11/lib/python3.11/site-packages/litestar/testing/websocket_test_session.py\", line 52, in __enter__\r\n    message = self.receive(timeout=self.client.timeout.read)\r\n              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n  File \"/Users/erik/Library/Caches/pypoetry/virtualenvs/*****-ocilal_E-py3.11/lib/python3.11/site-packages/litestar/testing/websocket_test_session.py\", line 195, in receive\r\n    raise WebSocketDisconnect(\r\nlitestar.exceptions.websocket_exceptions.WebSocketDisconnect: KeyError('method')\n```\n\n\n### Litestar Version\n\n2.0.0b2\n\n### Platform\n\n- [ ] Linux\n- [X] Mac\n- [ ] Windows\n- [ ] Other (Please specify in the description above)\n\n<!-- POLAR PLEDGE BADGE START -->\n## Funding\n* You can sponsor this specific effort via a [Polar.sh](https://polar.sh) pledge below\n* We receive the pledge once the issue is completed & verified\n\n<a href=\"https://polar.sh/litestar-org/litestar/issues/1934\">\n<picture>\n  <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://polar.sh/api/github/litestar-org/litestar/issues/1934/pledge.svg?darkmode=1\">\n  <img alt=\"Fund with Polar\" src=\"https://polar.sh/api/github/litestar-org/litestar/issues/1934/pledge.svg\">\n</picture>\n</a>\n<!-- POLAR PLEDGE BADGE END -->\n\n", "before_files": [{"content": "from __future__ import annotations\n\nimport time\nfrom functools import wraps\nfrom typing import TYPE_CHECKING, Any, Callable, ClassVar, cast\n\nfrom litestar.connection.request import Request\nfrom litestar.exceptions import MissingDependencyException\nfrom litestar.middleware.base import AbstractMiddleware\n\n__all__ = (\"PrometheusMiddleware\",)\n\nfrom litestar.status_codes import HTTP_500_INTERNAL_SERVER_ERROR\n\ntry:\n    import prometheus_client  # noqa: F401\nexcept ImportError as e:\n    raise MissingDependencyException(\"prometheus_client\") from e\n\nfrom prometheus_client import Counter, Gauge, Histogram\n\nif TYPE_CHECKING:\n    from prometheus_client.metrics import MetricWrapperBase\n\n    from litestar.contrib.prometheus import PrometheusConfig\n    from litestar.types import ASGIApp, Message, Receive, Scope, Send\n\n\nclass PrometheusMiddleware(AbstractMiddleware):\n    \"\"\"Prometheus Middleware.\"\"\"\n\n    _metrics: ClassVar[dict[str, MetricWrapperBase]] = {}\n\n    def __init__(self, app: ASGIApp, config: PrometheusConfig) -> None:\n        \"\"\"Middleware that adds Prometheus instrumentation to the application.\n\n        Args:\n            app: The ``next`` ASGI app to call.\n            config: An instance of :class:`PrometheusConfig <.contrib.prometheus.PrometheusConfig>`\n        \"\"\"\n        super().__init__(app=app, scopes=config.scopes, exclude=config.exclude, exclude_opt_key=config.exclude_opt_key)\n        self._config = config\n        self._kwargs: dict[str, Any] = {}\n\n        if self._config.buckets is not None:\n            self._kwargs[\"buckets\"] = self._config.buckets\n\n    def request_count(self, labels: dict[str, str | int | float]) -> Counter:\n        metric_name = f\"{self._config.prefix}_requests_total\"\n\n        if metric_name not in PrometheusMiddleware._metrics:\n            PrometheusMiddleware._metrics[metric_name] = Counter(\n                name=metric_name,\n                documentation=\"Total requests\",\n                labelnames=[*labels.keys()],\n            )\n\n        return cast(\"Counter\", PrometheusMiddleware._metrics[metric_name])\n\n    def request_time(self, labels: dict[str, str | int | float]) -> Histogram:\n        metric_name = f\"{self._config.prefix}_request_duration_seconds\"\n\n        if metric_name not in PrometheusMiddleware._metrics:\n            PrometheusMiddleware._metrics[metric_name] = Histogram(\n                name=metric_name,\n                documentation=\"Request duration, in seconds\",\n                labelnames=[*labels.keys()],\n                **self._kwargs,\n            )\n        return cast(\"Histogram\", PrometheusMiddleware._metrics[metric_name])\n\n    def requests_in_progress(self, labels: dict[str, str | int | float]) -> Gauge:\n        metric_name = f\"{self._config.prefix}_requests_in_progress\"\n\n        if metric_name not in PrometheusMiddleware._metrics:\n            PrometheusMiddleware._metrics[metric_name] = Gauge(\n                name=metric_name,\n                documentation=\"Total requests currently in progress\",\n                labelnames=[*labels.keys()],\n                multiprocess_mode=\"livesum\",\n            )\n        return cast(\"Gauge\", PrometheusMiddleware._metrics[metric_name])\n\n    def requests_error_count(self, labels: dict[str, str | int | float]) -> Counter:\n        metric_name = f\"{self._config.prefix}_requests_error_total\"\n\n        if metric_name not in PrometheusMiddleware._metrics:\n            PrometheusMiddleware._metrics[metric_name] = Counter(\n                name=metric_name,\n                documentation=\"Total errors in requests\",\n                labelnames=[*labels.keys()],\n            )\n        return cast(\"Counter\", PrometheusMiddleware._metrics[metric_name])\n\n    def _get_extra_labels(self, request: Request[Any, Any, Any]) -> dict[str, str]:\n        \"\"\"Get extra labels provided by the config and if they are callable, parse them.\n\n        Args:\n        request: The request object.\n\n        Returns:\n        A dictionary of extra labels.\n        \"\"\"\n\n        return {k: str(v(request) if callable(v) else v) for k, v in (self._config.labels or {}).items()}\n\n    def _get_default_labels(self, request: Request[Any, Any, Any]) -> dict[str, str | int | float]:\n        \"\"\"Get default label values from the request.\n\n        Args:\n            request: The request object.\n\n        Returns:\n            A dictionary of default labels.\n        \"\"\"\n\n        return {\n            \"method\": request.method,\n            \"path\": request.url.path,\n            \"status_code\": 200,\n            \"app_name\": self._config.app_name,\n        }\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        \"\"\"ASGI callable.\n\n        Args:\n            scope: The ASGI connection scope.\n            receive: The ASGI receive function.\n            send: The ASGI send function.\n\n        Returns:\n            None\n        \"\"\"\n\n        request = Request[Any, Any, Any](scope, receive)\n\n        if self._config.excluded_http_methods and request.method in self._config.excluded_http_methods:\n            await self.app(scope, receive, send)\n            return\n\n        labels = {**self._get_default_labels(request), **self._get_extra_labels(request)}\n\n        request_span = {\"start_time\": time.perf_counter(), \"end_time\": 0, \"duration\": 0, \"status_code\": 200}\n\n        wrapped_send = self._get_wrapped_send(send, request_span)\n\n        self.requests_in_progress(labels).labels(*labels.values()).inc()\n\n        try:\n            await self.app(scope, receive, wrapped_send)\n        finally:\n            extra: dict[str, Any] = {}\n            if self._config.exemplars:\n                extra[\"exemplar\"] = self._config.exemplars(request)\n\n            self.requests_in_progress(labels).labels(*labels.values()).dec()\n\n            labels[\"status_code\"] = request_span[\"status_code\"]\n            label_values = [*labels.values()]\n\n            if request_span[\"status_code\"] >= HTTP_500_INTERNAL_SERVER_ERROR:\n                self.requests_error_count(labels).labels(*label_values).inc(**extra)\n\n            self.request_count(labels).labels(*label_values).inc(**extra)\n            self.request_time(labels).labels(*label_values).observe(request_span[\"duration\"], **extra)\n\n    def _get_wrapped_send(self, send: Send, request_span: dict[str, float]) -> Callable:\n        @wraps(send)\n        async def wrapped_send(message: Message) -> None:\n            if message[\"type\"] == \"http.response.start\":\n                request_span[\"status_code\"] = message[\"status\"]\n\n            if message[\"type\"] == \"http.response.body\":\n                end = time.perf_counter()\n                request_span[\"duration\"] = end - request_span[\"start_time\"]\n                request_span[\"end_time\"] = end\n            await send(message)\n\n        return wrapped_send\n", "path": "litestar/contrib/prometheus/middleware.py"}], "after_files": [{"content": "from __future__ import annotations\n\nimport time\nfrom functools import wraps\nfrom typing import TYPE_CHECKING, Any, Callable, ClassVar, cast\n\nfrom litestar.connection.request import Request\nfrom litestar.enums import ScopeType\nfrom litestar.exceptions import MissingDependencyException\nfrom litestar.middleware.base import AbstractMiddleware\n\n__all__ = (\"PrometheusMiddleware\",)\n\nfrom litestar.status_codes import HTTP_500_INTERNAL_SERVER_ERROR\n\ntry:\n    import prometheus_client  # noqa: F401\nexcept ImportError as e:\n    raise MissingDependencyException(\"prometheus_client\") from e\n\nfrom prometheus_client import Counter, Gauge, Histogram\n\nif TYPE_CHECKING:\n    from prometheus_client.metrics import MetricWrapperBase\n\n    from litestar.contrib.prometheus import PrometheusConfig\n    from litestar.types import ASGIApp, Message, Receive, Scope, Send\n\n\nclass PrometheusMiddleware(AbstractMiddleware):\n    \"\"\"Prometheus Middleware.\"\"\"\n\n    _metrics: ClassVar[dict[str, MetricWrapperBase]] = {}\n\n    def __init__(self, app: ASGIApp, config: PrometheusConfig) -> None:\n        \"\"\"Middleware that adds Prometheus instrumentation to the application.\n\n        Args:\n            app: The ``next`` ASGI app to call.\n            config: An instance of :class:`PrometheusConfig <.contrib.prometheus.PrometheusConfig>`\n        \"\"\"\n        super().__init__(app=app, scopes=config.scopes, exclude=config.exclude, exclude_opt_key=config.exclude_opt_key)\n        self._config = config\n        self._kwargs: dict[str, Any] = {}\n\n        if self._config.buckets is not None:\n            self._kwargs[\"buckets\"] = self._config.buckets\n\n    def request_count(self, labels: dict[str, str | int | float]) -> Counter:\n        metric_name = f\"{self._config.prefix}_requests_total\"\n\n        if metric_name not in PrometheusMiddleware._metrics:\n            PrometheusMiddleware._metrics[metric_name] = Counter(\n                name=metric_name,\n                documentation=\"Total requests\",\n                labelnames=[*labels.keys()],\n            )\n\n        return cast(\"Counter\", PrometheusMiddleware._metrics[metric_name])\n\n    def request_time(self, labels: dict[str, str | int | float]) -> Histogram:\n        metric_name = f\"{self._config.prefix}_request_duration_seconds\"\n\n        if metric_name not in PrometheusMiddleware._metrics:\n            PrometheusMiddleware._metrics[metric_name] = Histogram(\n                name=metric_name,\n                documentation=\"Request duration, in seconds\",\n                labelnames=[*labels.keys()],\n                **self._kwargs,\n            )\n        return cast(\"Histogram\", PrometheusMiddleware._metrics[metric_name])\n\n    def requests_in_progress(self, labels: dict[str, str | int | float]) -> Gauge:\n        metric_name = f\"{self._config.prefix}_requests_in_progress\"\n\n        if metric_name not in PrometheusMiddleware._metrics:\n            PrometheusMiddleware._metrics[metric_name] = Gauge(\n                name=metric_name,\n                documentation=\"Total requests currently in progress\",\n                labelnames=[*labels.keys()],\n                multiprocess_mode=\"livesum\",\n            )\n        return cast(\"Gauge\", PrometheusMiddleware._metrics[metric_name])\n\n    def requests_error_count(self, labels: dict[str, str | int | float]) -> Counter:\n        metric_name = f\"{self._config.prefix}_requests_error_total\"\n\n        if metric_name not in PrometheusMiddleware._metrics:\n            PrometheusMiddleware._metrics[metric_name] = Counter(\n                name=metric_name,\n                documentation=\"Total errors in requests\",\n                labelnames=[*labels.keys()],\n            )\n        return cast(\"Counter\", PrometheusMiddleware._metrics[metric_name])\n\n    def _get_extra_labels(self, request: Request[Any, Any, Any]) -> dict[str, str]:\n        \"\"\"Get extra labels provided by the config and if they are callable, parse them.\n\n        Args:\n        request: The request object.\n\n        Returns:\n        A dictionary of extra labels.\n        \"\"\"\n\n        return {k: str(v(request) if callable(v) else v) for k, v in (self._config.labels or {}).items()}\n\n    def _get_default_labels(self, request: Request[Any, Any, Any]) -> dict[str, str | int | float]:\n        \"\"\"Get default label values from the request.\n\n        Args:\n            request: The request object.\n\n        Returns:\n            A dictionary of default labels.\n        \"\"\"\n\n        return {\n            \"method\": request.method if request.scope[\"type\"] == ScopeType.HTTP else request.scope[\"type\"],\n            \"path\": request.url.path,\n            \"status_code\": 200,\n            \"app_name\": self._config.app_name,\n        }\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        \"\"\"ASGI callable.\n\n        Args:\n            scope: The ASGI connection scope.\n            receive: The ASGI receive function.\n            send: The ASGI send function.\n\n        Returns:\n            None\n        \"\"\"\n\n        request = Request[Any, Any, Any](scope, receive)\n\n        if self._config.excluded_http_methods and request.method in self._config.excluded_http_methods:\n            await self.app(scope, receive, send)\n            return\n\n        labels = {**self._get_default_labels(request), **self._get_extra_labels(request)}\n\n        request_span = {\"start_time\": time.perf_counter(), \"end_time\": 0, \"duration\": 0, \"status_code\": 200}\n\n        wrapped_send = self._get_wrapped_send(send, request_span)\n\n        self.requests_in_progress(labels).labels(*labels.values()).inc()\n\n        try:\n            await self.app(scope, receive, wrapped_send)\n        finally:\n            extra: dict[str, Any] = {}\n            if self._config.exemplars:\n                extra[\"exemplar\"] = self._config.exemplars(request)\n\n            self.requests_in_progress(labels).labels(*labels.values()).dec()\n\n            labels[\"status_code\"] = request_span[\"status_code\"]\n            label_values = [*labels.values()]\n\n            if request_span[\"status_code\"] >= HTTP_500_INTERNAL_SERVER_ERROR:\n                self.requests_error_count(labels).labels(*label_values).inc(**extra)\n\n            self.request_count(labels).labels(*label_values).inc(**extra)\n            self.request_time(labels).labels(*label_values).observe(request_span[\"duration\"], **extra)\n\n    def _get_wrapped_send(self, send: Send, request_span: dict[str, float]) -> Callable:\n        @wraps(send)\n        async def wrapped_send(message: Message) -> None:\n            if message[\"type\"] == \"http.response.start\":\n                request_span[\"status_code\"] = message[\"status\"]\n\n            if message[\"type\"] == \"http.response.body\":\n                end = time.perf_counter()\n                request_span[\"duration\"] = end - request_span[\"start_time\"]\n                request_span[\"end_time\"] = end\n            await send(message)\n\n        return wrapped_send\n", "path": "litestar/contrib/prometheus/middleware.py"}]}