Skip to content

Commit d65bb88

Browse files
committed
Link metrics to middleware isntances.
The other side effect of this chance is that we delay the initialization and registration of the metrics object to the point in which Django creates the middleware instances. This will allow for customization of the metrics, for example custom latency buckets. See #75
1 parent 2f59ac4 commit d65bb88

File tree

1 file changed

+194
-138
lines changed

1 file changed

+194
-138
lines changed

django_prometheus/middleware.py

Lines changed: 194 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -3,143 +3,191 @@
33
from django.utils.deprecation import MiddlewareMixin
44
from django_prometheus.utils import PowersOf, Time, TimeSince
55

6-
requests_total = Counter(
7-
"django_http_requests_before_middlewares_total",
8-
"Total count of requests before middlewares run.",
9-
)
10-
responses_total = Counter(
11-
"django_http_responses_before_middlewares_total",
12-
"Total count of responses before middlewares run.",
13-
)
14-
requests_latency_before = Histogram(
15-
"django_http_requests_latency_including_middlewares_seconds",
16-
(
17-
"Histogram of requests processing time (including middleware "
18-
"processing time)."
19-
),
20-
)
21-
requests_unknown_latency_before = Counter(
22-
"django_http_requests_unknown_latency_including_middlewares_total",
23-
(
24-
"Count of requests for which the latency was unknown (when computing "
25-
"django_http_requests_latency_including_middlewares_seconds)."
26-
),
27-
)
286

7+
def _register_metric(cls, name, documentation, labelnames=tuple(), **kwargs):
8+
return cls(name, documentation, labelnames=labelnames, **kwargs)
299

30-
class PrometheusBeforeMiddleware(MiddlewareMixin):
3110

11+
class Metrics:
12+
_instance = None
13+
14+
@classmethod
15+
def get_instance(cls):
16+
if not cls._instance:
17+
cls._instance = cls()
18+
return cls._instance
19+
20+
def __init__(self, *args, **kwargs):
21+
self.register()
22+
23+
def register(self):
24+
self.requests_total = _register_metric(
25+
Counter,
26+
"django_http_requests_before_middlewares_total",
27+
"Total count of requests before middlewares run.",
28+
)
29+
self.responses_total = _register_metric(
30+
Counter,
31+
"django_http_responses_before_middlewares_total",
32+
"Total count of responses before middlewares run.",
33+
)
34+
self.requests_latency_before = _register_metric(
35+
Histogram,
36+
"django_http_requests_latency_including_middlewares_seconds",
37+
(
38+
"Histogram of requests processing time (including middleware "
39+
"processing time)."
40+
),
41+
)
42+
self.requests_unknown_latency_before = _register_metric(
43+
Counter,
44+
"django_http_requests_unknown_latency_including_middlewares_total",
45+
(
46+
"Count of requests for which the latency was unknown (when computing "
47+
"django_http_requests_latency_including_middlewares_seconds)."
48+
),
49+
)
50+
self.requests_latency_by_view_method = _register_metric(
51+
Histogram,
52+
"django_http_requests_latency_seconds_by_view_method",
53+
"Histogram of request processing time labelled by view.",
54+
["view", "method"],
55+
buckets=(
56+
0.01,
57+
0.025,
58+
0.05,
59+
0.075,
60+
0.1,
61+
0.25,
62+
0.5,
63+
0.75,
64+
1.0,
65+
2.5,
66+
5.0,
67+
7.5,
68+
10.0,
69+
25.0,
70+
50.0,
71+
75.0,
72+
float("inf"),
73+
),
74+
)
75+
self.requests_unknown_latency = _register_metric(
76+
Counter,
77+
"django_http_requests_unknown_latency_total",
78+
"Count of requests for which the latency was unknown.",
79+
)
80+
# Set in process_request
81+
self.requests_ajax = _register_metric(
82+
Counter, "django_http_ajax_requests_total", "Count of AJAX requests."
83+
)
84+
self.requests_by_method = _register_metric(
85+
Counter,
86+
"django_http_requests_total_by_method",
87+
"Count of requests by method.",
88+
["method"],
89+
)
90+
self.requests_by_transport = _register_metric(
91+
Counter,
92+
"django_http_requests_total_by_transport",
93+
"Count of requests by transport.",
94+
["transport"],
95+
)
96+
# Set in process_view
97+
self.requests_by_view_transport_method = _register_metric(
98+
Counter,
99+
"django_http_requests_total_by_view_transport_method",
100+
"Count of requests by view, transport, method.",
101+
["view", "transport", "method"],
102+
)
103+
self.requests_body_bytes = _register_metric(
104+
Histogram,
105+
"django_http_requests_body_total_bytes",
106+
"Histogram of requests by body size.",
107+
buckets=PowersOf(2, 30),
108+
)
109+
# Set in process_template_response
110+
self.responses_by_templatename = _register_metric(
111+
Counter,
112+
"django_http_responses_total_by_templatename",
113+
"Count of responses by template name.",
114+
["templatename"],
115+
)
116+
# Set in process_response
117+
self.responses_by_status = _register_metric(
118+
Counter,
119+
"django_http_responses_total_by_status",
120+
"Count of responses by status.",
121+
["status"],
122+
)
123+
self.responses_by_status_view_method = _register_metric(
124+
Counter,
125+
"django_http_responses_total_by_status_view_method",
126+
"Count of responses by status, view, method.",
127+
["status", "view", "method"],
128+
)
129+
self.responses_body_bytes = _register_metric(
130+
Histogram,
131+
"django_http_responses_body_total_bytes",
132+
"Histogram of responses by body size.",
133+
buckets=PowersOf(2, 30),
134+
)
135+
self.responses_by_charset = _register_metric(
136+
Counter,
137+
"django_http_responses_total_by_charset",
138+
"Count of responses by charset.",
139+
["charset"],
140+
)
141+
self.responses_streaming = _register_metric(
142+
Counter,
143+
"django_http_responses_streaming_total",
144+
"Count of streaming responses.",
145+
)
146+
# Set in process_exception
147+
self.exceptions_by_type = _register_metric(
148+
Counter,
149+
"django_http_exceptions_total_by_type",
150+
"Count of exceptions by object type.",
151+
["type"],
152+
)
153+
self.exceptions_by_view = _register_metric(
154+
Counter,
155+
"django_http_exceptions_total_by_view",
156+
"Count of exceptions by view.",
157+
["view_name"],
158+
)
159+
160+
161+
class PrometheusBeforeMiddleware(MiddlewareMixin):
32162
"""Monitoring middleware that should run before other middlewares."""
33163

164+
def __init__(self, get_response=None):
165+
super(PrometheusBeforeMiddleware, self).__init__(get_response)
166+
self.metrics = Metrics.get_instance()
167+
34168
def process_request(self, request):
35-
requests_total.inc()
169+
self.metrics.requests_total.inc()
36170
request.prometheus_before_middleware_event = Time()
37171

38172
def process_response(self, request, response):
39-
responses_total.inc()
173+
self.metrics.responses_total.inc()
40174
if hasattr(request, "prometheus_before_middleware_event"):
41-
requests_latency_before.observe(
175+
self.metrics.requests_latency_before.observe(
42176
TimeSince(request.prometheus_before_middleware_event)
43177
)
44178
else:
45-
requests_unknown_latency_before.inc()
179+
self.metrics.requests_unknown_latency_before.inc()
46180
return response
47181

48182

49-
requests_latency_by_view_method = Histogram(
50-
"django_http_requests_latency_seconds_by_view_method",
51-
"Histogram of request processing time labelled by view.",
52-
["view", "method"],
53-
buckets=(
54-
0.01,
55-
0.025,
56-
0.05,
57-
0.075,
58-
0.1,
59-
0.25,
60-
0.5,
61-
0.75,
62-
1.0,
63-
2.5,
64-
5.0,
65-
7.5,
66-
10.0,
67-
25.0,
68-
50.0,
69-
75.0,
70-
float("inf"),
71-
),
72-
)
73-
requests_unknown_latency = Counter(
74-
"django_http_requests_unknown_latency_total",
75-
"Count of requests for which the latency was unknown.",
76-
)
77-
# Set in process_request
78-
ajax_requests = Counter("django_http_ajax_requests_total", "Count of AJAX requests.")
79-
requests_by_method = Counter(
80-
"django_http_requests_total_by_method", "Count of requests by method.", ["method"]
81-
)
82-
requests_by_transport = Counter(
83-
"django_http_requests_total_by_transport",
84-
"Count of requests by transport.",
85-
["transport"],
86-
)
87-
# Set in process_view
88-
requests_by_view_transport_method = Counter(
89-
"django_http_requests_total_by_view_transport_method",
90-
"Count of requests by view, transport, method.",
91-
["view", "transport", "method"],
92-
)
93-
requests_body_bytes = Histogram(
94-
"django_http_requests_body_total_bytes",
95-
"Histogram of requests by body size.",
96-
buckets=PowersOf(2, 30),
97-
)
98-
# Set in process_template_response
99-
responses_by_templatename = Counter(
100-
"django_http_responses_total_by_templatename",
101-
"Count of responses by template name.",
102-
["templatename"],
103-
)
104-
# Set in process_response
105-
responses_by_status = Counter(
106-
"django_http_responses_total_by_status", "Count of responses by status.", ["status"]
107-
)
108-
responses_by_status_view_method = Counter(
109-
"django_http_responses_total_by_status_view_method",
110-
"Count of responses by status, view, method.",
111-
["status", "view", "method"],
112-
)
113-
responses_body_bytes = Histogram(
114-
"django_http_responses_body_total_bytes",
115-
"Histogram of responses by body size.",
116-
buckets=PowersOf(2, 30),
117-
)
118-
responses_by_charset = Counter(
119-
"django_http_responses_total_by_charset",
120-
"Count of responses by charset.",
121-
["charset"],
122-
)
123-
responses_streaming = Counter(
124-
"django_http_responses_streaming_total", "Count of streaming responses."
125-
)
126-
# Set in process_exception
127-
exceptions_by_type = Counter(
128-
"django_http_exceptions_total_by_type",
129-
"Count of exceptions by object type.",
130-
["type"],
131-
)
132-
exceptions_by_view = Counter(
133-
"django_http_exceptions_total_by_view",
134-
"Count of exceptions by view.",
135-
["view_name"],
136-
)
137-
138-
139183
class PrometheusAfterMiddleware(MiddlewareMixin):
140184

141185
"""Monitoring middleware that should run after other middlewares."""
142186

187+
def __init__(self, get_response=None):
188+
super(PrometheusAfterMiddleware, self).__init__(get_response)
189+
self.metrics = Metrics.get_instance()
190+
143191
def _transport(self, request):
144192
return "https" if request.is_secure() else "http"
145193

@@ -162,12 +210,12 @@ def _method(self, request):
162210
def process_request(self, request):
163211
transport = self._transport(request)
164212
method = self._method(request)
165-
requests_by_method.labels(method).inc()
166-
requests_by_transport.labels(transport).inc()
213+
self.metrics.requests_by_method.labels(method=method).inc()
214+
self.metrics.requests_by_transport.labels(transport=transport).inc()
167215
if request.is_ajax():
168-
ajax_requests.inc()
216+
self.metrics.requests_ajax.inc()
169217
content_length = int(request.META.get("CONTENT_LENGTH") or 0)
170-
requests_body_bytes.observe(content_length)
218+
self.metrics.requests_body_bytes.observe(content_length)
171219
request.prometheus_after_middleware_event = Time()
172220

173221
def _get_view_name(self, request):
@@ -183,41 +231,49 @@ def process_view(self, request, view_func, *view_args, **view_kwargs):
183231
method = self._method(request)
184232
if hasattr(request, "resolver_match"):
185233
name = request.resolver_match.view_name or "<unnamed view>"
186-
requests_by_view_transport_method.labels(name, transport, method).inc()
234+
self.metrics.requests_by_view_transport_method.labels(
235+
view=name, transport=transport, method=method
236+
).inc()
187237

188238
def process_template_response(self, request, response):
189239
if hasattr(response, "template_name"):
190-
responses_by_templatename.labels(str(response.template_name)).inc()
240+
self.metrics.responses_by_templatename.labels(
241+
templatename=str(response.template_name)
242+
).inc()
191243
return response
192244

193245
def process_response(self, request, response):
194246
method = self._method(request)
195247
name = self._get_view_name(request)
196-
197-
responses_by_status.labels(str(response.status_code)).inc()
198-
responses_by_status_view_method.labels(response.status_code, name, method).inc()
248+
status = str(response.status_code)
249+
self.metrics.responses_by_status.labels(status=status).inc()
250+
self.metrics.responses_by_status_view_method.labels(
251+
status=status, view=name, method=method
252+
).inc()
199253
if hasattr(response, "charset"):
200-
responses_by_charset.labels(str(response.charset)).inc()
254+
self.metrics.responses_by_charset.labels(
255+
charset=str(response.charset)
256+
).inc()
201257
if hasattr(response, "streaming") and response.streaming:
202-
responses_streaming.inc()
258+
self.metrics.responses_streaming.inc()
203259
if hasattr(response, "content"):
204-
responses_body_bytes.observe(len(response.content))
260+
self.metrics.responses_body_bytes.observe(len(response.content))
205261
if hasattr(request, "prometheus_after_middleware_event"):
206-
requests_latency_by_view_method.labels(
207-
view=name, method=request.method
262+
self.metrics.requests_latency_by_view_method.labels(
263+
view=self._get_view_name(request), method=request.method
208264
).observe(TimeSince(request.prometheus_after_middleware_event))
209265
else:
210-
requests_unknown_latency.inc()
266+
self.metrics.requests_unknown_latency.inc()
211267
return response
212268

213269
def process_exception(self, request, exception):
214-
name = self._get_view_name(request)
215-
exceptions_by_type.labels(type(exception).__name__).inc()
270+
self.metrics.exceptions_by_type.labels(type=type(exception).__name__).inc()
216271
if hasattr(request, "resolver_match"):
217-
exceptions_by_view.labels(name).inc()
272+
name = request.resolver_match.view_name or "<unnamed view>"
273+
self.metrics.exceptions_by_view.labels(view_name=name).inc()
218274
if hasattr(request, "prometheus_after_middleware_event"):
219-
requests_latency_by_view_method.labels(
220-
view=name, method=request.method
275+
self.metrics.requests_latency_by_view_method.labels(
276+
view=self._get_view_name(request), method=request.method
221277
).observe(TimeSince(request.prometheus_after_middleware_event))
222278
else:
223-
requests_unknown_latency.inc()
279+
self.metrics.requests_unknown_latency.inc()

0 commit comments

Comments
 (0)