Skip to content

Commit 48665a6

Browse files
author
Hisham ElSheshtawy
committed
Use dedicated registry instead of the default one
1 parent 1a2cb56 commit 48665a6

File tree

1 file changed

+36
-13
lines changed

1 file changed

+36
-13
lines changed

quart_prometheus_logger.py

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33

44
import logging
55
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Union
6-
from prometheus_client import CONTENT_TYPE_LATEST, REGISTRY, Counter, Histogram, generate_latest
6+
7+
from prometheus_client import CONTENT_TYPE_LATEST, Counter, Histogram, generate_latest, CollectorRegistry
78
from quart import Response, g, request
89
from quart.exceptions import HTTPException, HTTPStatusException
9-
from .utils import now_utc
10+
11+
from utils import now_utc
1012

1113
if TYPE_CHECKING:
1214
from quart import Quart
@@ -66,14 +68,15 @@ def __init__(self, app: Optional[Quart] = None, metrics_endpoint: str = "root"):
6668
self._collectors: Dict[str, MetricType] = {}
6769
self._custom_labeler: Optional[Callable[["LocalProxy"], Dict[str, str]]] = None
6870
self._custom_label_names: List[str] = []
71+
self._registry = CollectorRegistry()
6972
self._register_collectors()
7073
if app:
7174
self.init_app(app, metrics_endpoint)
7275

7376
def init_app(self, app: Quart, metrics_endpoint: str):
7477
"""Register an application."""
7578

76-
def start_request():
79+
def prometheus_before_request_callback():
7780
if request.path == "/metrics":
7881
return
7982
g.start = now_utc() # type: ignore # This is a valid use of Quart's global object
@@ -82,7 +85,7 @@ def start_request():
8285
request.content_length or 0
8386
)
8487

85-
def end_request(response):
88+
def prometheus_after_request_callback(response: Response):
8689
if request.path == "/metrics":
8790
return response
8891
if not hasattr(g, "start"):
@@ -105,17 +108,27 @@ def end_request(response):
105108
).inc()
106109
return response
107110

108-
def abort_with_error(exc: Union[HTTPException, Exception]) -> Response:
111+
def prometheus_error_handler(exc: Union[HTTPException, Exception]) -> Response:
109112
if isinstance(exc, HTTPException):
110113
response = exc.get_response()
111114
else:
112115
response = Response("", 500)
113-
return end_request(response)
116+
return prometheus_after_request_callback(response)
114117

115118
self.app = app
116-
app.before_request(start_request)
117-
app.after_request(end_request)
118-
app.register_error_handler(HTTPStatusException, abort_with_error)
119+
app.before_request(
120+
prometheus_before_request_callback,
121+
prometheus_before_request_callback.__name__,
122+
)
123+
app.after_request(
124+
prometheus_after_request_callback,
125+
prometheus_after_request_callback.__name__,
126+
)
127+
app.register_error_handler(
128+
HTTPStatusException,
129+
prometheus_error_handler, # type:ignore
130+
prometheus_error_handler.__name__,
131+
)
119132
app.add_url_rule("/metrics", metrics_endpoint, view_func=self.render)
120133

121134
def _register_collectors(self):
@@ -127,46 +140,57 @@ def _register_collectors(self):
127140
"http_requests",
128141
"Total number of requests",
129142
["method", "path", "status", *self._custom_label_names],
143+
registry=self._registry,
130144
),
131145
Counter(
132146
"http_requests_errors",
133147
"Total number of error requests",
134148
["method", "path", "status", *self._custom_label_names],
149+
registry=self._registry,
135150
),
136151
Histogram(
137152
"http_request_duration_seconds",
138153
"The amount of time spent handling requests",
139154
["path", *self._custom_label_names],
155+
registry=self._registry,
140156
),
141157
Histogram(
142158
"http_request_size_bytes",
143159
"The size of requests",
144160
["path", *self._custom_label_names],
145161
buckets=REQUEST_BUCKETS,
162+
registry=self._registry,
146163
),
147164
Histogram(
148165
"http_response_size_bytes",
149166
"The size of responses",
150167
["path", *self._custom_label_names],
151168
buckets=RESPONSE_BUCKETS,
169+
registry=self._registry,
152170
),
153171
)
154172
}
155173

174+
def _reset_metrics(self):
175+
"""Unregister all collectors from the registry."""
176+
for _, collector in self._collectors.items():
177+
self._registry.unregister(collector)
178+
156179
def custom_route_labeler(
157180
self, labeler: Callable[["LocalProxy"], Dict[str, str]], label_names: List[str]
158181
) -> None:
159182
"""Add a handler for providing additional labels for a route.
160183
161184
This will reset all metrics. Conventionally it's called when the extension is first registered
162185
163-
:param labeler: The handler function to invoke. It must return a dict of key-value labels.
186+
:param labeler: The handler function to invoke. It takes a Quart LocalProxy representing
187+
the request, from which values for the custom label can be pulled. It must return
188+
a dict of key-value labels.
164189
:param label_names: The possible label names emitted by the labeler.
165190
"""
166191
self._custom_labeler = labeler
167192
self._custom_label_names = label_names
168-
for _, collector in self._collectors.items():
169-
REGISTRY.unregister(collector)
193+
self._reset_metrics()
170194
self._register_collectors()
171195

172196
def get(self, name: str) -> MetricType:
@@ -181,4 +205,3 @@ def get(self, name: str) -> MetricType:
181205
def render():
182206
"""Render the stats."""
183207
return Response(generate_latest(), mimetype=CONTENT_TYPE_LATEST)
184-

0 commit comments

Comments
 (0)