Skip to content

Commit 143fae3

Browse files
authored
fix(prometheus)!: default group_path to True to bound metric cardinality (#4925)
fix(prometheus): default group_path to True to bound metric cardinality PrometheusMiddleware recorded the raw request path as the path metric label. On a route with path parameters, every distinct value seen in traffic became a new time series, so the metric set grew without bound. group_path=True already avoided this by using the route template instead of the raw path, but it was opt-in, so users hit the cardinality trap silently by default. Flip the default. Raw-path behavior stays available via group_path=False for anyone who wants it. Fixes #4891
1 parent 2453f45 commit 143fae3

3 files changed

Lines changed: 28 additions & 2 deletions

File tree

docs/examples/plugins/prometheus/using_prometheus_exporter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from litestar.plugins.prometheus import PrometheusConfig, PrometheusController
33

44

5-
def create_app(group_path: bool = False) -> Litestar:
5+
def create_app(group_path: bool = True) -> Litestar:
66
# Default app name and prefix is litestar.
77
prometheus_config = PrometheusConfig(group_path=group_path)
88

litestar/plugins/prometheus/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ class PrometheusConfig:
5252
middleware_class: type[PrometheusMiddleware] = field(default=PrometheusMiddleware)
5353
"""The middleware class to use.
5454
"""
55-
group_path: bool = field(default=False)
55+
group_path: bool = field(default=True)
5656
"""Whether to group paths in the metrics to avoid cardinality explosion.
5757
"""
5858

tests/unit/test_plugins/test_prometheus.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
from litestar import get, post, websocket_listener
1212
from litestar.exceptions import HTTPException, NotAuthorizedException, PermissionDeniedException
13+
from litestar.params import FromPath
1314
from litestar.plugins.prometheus import PrometheusConfig, PrometheusController, PrometheusMiddleware
1415
from litestar.status_codes import HTTP_200_OK
1516
from litestar.testing import create_test_client
@@ -99,6 +100,31 @@ def handler_error() -> dict:
99100
)
100101

101102

103+
def test_prometheus_group_path_defaults_to_true() -> None:
104+
assert PrometheusConfig().group_path is True
105+
106+
107+
def test_prometheus_group_path_default_uses_route_template_for_parameterized_routes() -> None:
108+
config = create_config()
109+
110+
@get("/users/{user_id:int}")
111+
def get_user(user_id: FromPath[int]) -> dict:
112+
return {"user_id": user_id}
113+
114+
with create_test_client([get_user, PrometheusController], middleware=[config.middleware]) as client:
115+
for user_id in range(1, 4):
116+
client.get(f"/users/{user_id}")
117+
metrics = client.get("/metrics").content.decode()
118+
119+
assert (
120+
"""litestar_requests_total{app_name="litestar",method="GET",path="/users/{user_id}",status_code="200"} 3.0"""
121+
in metrics
122+
)
123+
assert 'path="/users/1"' not in metrics
124+
assert 'path="/users/2"' not in metrics
125+
assert 'path="/users/3"' not in metrics
126+
127+
102128
def test_prometheus_middleware_configurations() -> None:
103129
labels = {"foo": "bar", "baz": lambda a: "qux"}
104130

0 commit comments

Comments
 (0)