Updates
This commit is contained in:
@@ -0,0 +1,309 @@
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
from prometheus_client import Counter, Histogram
|
||||
|
||||
from django_prometheus.conf import NAMESPACE, PROMETHEUS_LATENCY_BUCKETS
|
||||
from django_prometheus.utils import PowersOf, Time, TimeSince
|
||||
|
||||
|
||||
class Metrics:
|
||||
_instance = None
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls):
|
||||
if not cls._instance:
|
||||
cls._instance = cls()
|
||||
return cls._instance
|
||||
|
||||
def register_metric(self, metric_cls, name, documentation, labelnames=(), **kwargs):
|
||||
return metric_cls(name, documentation, labelnames=labelnames, **kwargs)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.register()
|
||||
|
||||
def register(self):
|
||||
self.requests_total = self.register_metric(
|
||||
Counter,
|
||||
"django_http_requests_before_middlewares_total",
|
||||
"Total count of requests before middlewares run.",
|
||||
namespace=NAMESPACE,
|
||||
)
|
||||
self.responses_total = self.register_metric(
|
||||
Counter,
|
||||
"django_http_responses_before_middlewares_total",
|
||||
"Total count of responses before middlewares run.",
|
||||
namespace=NAMESPACE,
|
||||
)
|
||||
self.requests_latency_before = self.register_metric(
|
||||
Histogram,
|
||||
"django_http_requests_latency_including_middlewares_seconds",
|
||||
("Histogram of requests processing time (including middleware processing time)."),
|
||||
buckets=PROMETHEUS_LATENCY_BUCKETS,
|
||||
namespace=NAMESPACE,
|
||||
)
|
||||
self.requests_unknown_latency_before = self.register_metric(
|
||||
Counter,
|
||||
"django_http_requests_unknown_latency_including_middlewares_total",
|
||||
(
|
||||
"Count of requests for which the latency was unknown (when computing "
|
||||
"django_http_requests_latency_including_middlewares_seconds)."
|
||||
),
|
||||
namespace=NAMESPACE,
|
||||
)
|
||||
self.requests_latency_by_view_method = self.register_metric(
|
||||
Histogram,
|
||||
"django_http_requests_latency_seconds_by_view_method",
|
||||
"Histogram of request processing time labelled by view.",
|
||||
["view", "method"],
|
||||
buckets=PROMETHEUS_LATENCY_BUCKETS,
|
||||
namespace=NAMESPACE,
|
||||
)
|
||||
self.requests_unknown_latency = self.register_metric(
|
||||
Counter,
|
||||
"django_http_requests_unknown_latency_total",
|
||||
"Count of requests for which the latency was unknown.",
|
||||
namespace=NAMESPACE,
|
||||
)
|
||||
# Set in process_request
|
||||
self.requests_ajax = self.register_metric(
|
||||
Counter,
|
||||
"django_http_ajax_requests_total",
|
||||
"Count of AJAX requests.",
|
||||
namespace=NAMESPACE,
|
||||
)
|
||||
self.requests_by_method = self.register_metric(
|
||||
Counter,
|
||||
"django_http_requests_total_by_method",
|
||||
"Count of requests by method.",
|
||||
["method"],
|
||||
namespace=NAMESPACE,
|
||||
)
|
||||
self.requests_by_transport = self.register_metric(
|
||||
Counter,
|
||||
"django_http_requests_total_by_transport",
|
||||
"Count of requests by transport.",
|
||||
["transport"],
|
||||
namespace=NAMESPACE,
|
||||
)
|
||||
# Set in process_view
|
||||
self.requests_by_view_transport_method = self.register_metric(
|
||||
Counter,
|
||||
"django_http_requests_total_by_view_transport_method",
|
||||
"Count of requests by view, transport, method.",
|
||||
["view", "transport", "method"],
|
||||
namespace=NAMESPACE,
|
||||
)
|
||||
self.requests_body_bytes = self.register_metric(
|
||||
Histogram,
|
||||
"django_http_requests_body_total_bytes",
|
||||
"Histogram of requests by body size.",
|
||||
buckets=PowersOf(2, 30),
|
||||
namespace=NAMESPACE,
|
||||
)
|
||||
# Set in process_template_response
|
||||
self.responses_by_templatename = self.register_metric(
|
||||
Counter,
|
||||
"django_http_responses_total_by_templatename",
|
||||
"Count of responses by template name.",
|
||||
["templatename"],
|
||||
namespace=NAMESPACE,
|
||||
)
|
||||
# Set in process_response
|
||||
self.responses_by_status = self.register_metric(
|
||||
Counter,
|
||||
"django_http_responses_total_by_status",
|
||||
"Count of responses by status.",
|
||||
["status"],
|
||||
namespace=NAMESPACE,
|
||||
)
|
||||
self.responses_by_status_view_method = self.register_metric(
|
||||
Counter,
|
||||
"django_http_responses_total_by_status_view_method",
|
||||
"Count of responses by status, view, method.",
|
||||
["status", "view", "method"],
|
||||
namespace=NAMESPACE,
|
||||
)
|
||||
self.responses_body_bytes = self.register_metric(
|
||||
Histogram,
|
||||
"django_http_responses_body_total_bytes",
|
||||
"Histogram of responses by body size.",
|
||||
buckets=PowersOf(2, 30),
|
||||
namespace=NAMESPACE,
|
||||
)
|
||||
self.responses_by_charset = self.register_metric(
|
||||
Counter,
|
||||
"django_http_responses_total_by_charset",
|
||||
"Count of responses by charset.",
|
||||
["charset"],
|
||||
namespace=NAMESPACE,
|
||||
)
|
||||
self.responses_streaming = self.register_metric(
|
||||
Counter,
|
||||
"django_http_responses_streaming_total",
|
||||
"Count of streaming responses.",
|
||||
namespace=NAMESPACE,
|
||||
)
|
||||
# Set in process_exception
|
||||
self.exceptions_by_type = self.register_metric(
|
||||
Counter,
|
||||
"django_http_exceptions_total_by_type",
|
||||
"Count of exceptions by object type.",
|
||||
["type"],
|
||||
namespace=NAMESPACE,
|
||||
)
|
||||
self.exceptions_by_view = self.register_metric(
|
||||
Counter,
|
||||
"django_http_exceptions_total_by_view",
|
||||
"Count of exceptions by view.",
|
||||
["view"],
|
||||
namespace=NAMESPACE,
|
||||
)
|
||||
|
||||
|
||||
class PrometheusBeforeMiddleware(MiddlewareMixin):
|
||||
"""Monitoring middleware that should run before other middlewares."""
|
||||
|
||||
metrics_cls = Metrics
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.metrics = self.metrics_cls.get_instance()
|
||||
|
||||
def process_request(self, request):
|
||||
self.metrics.requests_total.inc()
|
||||
request.prometheus_before_middleware_event = Time()
|
||||
|
||||
def process_response(self, request, response):
|
||||
self.metrics.responses_total.inc()
|
||||
if hasattr(request, "prometheus_before_middleware_event"):
|
||||
self.metrics.requests_latency_before.observe(TimeSince(request.prometheus_before_middleware_event))
|
||||
else:
|
||||
self.metrics.requests_unknown_latency_before.inc()
|
||||
return response
|
||||
|
||||
|
||||
class PrometheusAfterMiddleware(MiddlewareMixin):
|
||||
"""Monitoring middleware that should run after other middlewares."""
|
||||
|
||||
metrics_cls = Metrics
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.metrics = self.metrics_cls.get_instance()
|
||||
|
||||
def _transport(self, request):
|
||||
return "https" if request.is_secure() else "http"
|
||||
|
||||
def _method(self, request):
|
||||
m = request.method
|
||||
if m not in (
|
||||
"GET",
|
||||
"HEAD",
|
||||
"POST",
|
||||
"PUT",
|
||||
"DELETE",
|
||||
"TRACE",
|
||||
"OPTIONS",
|
||||
"CONNECT",
|
||||
"PATCH",
|
||||
):
|
||||
return "<invalid method>"
|
||||
return m
|
||||
|
||||
def label_metric(self, metric, request, response=None, **labels):
|
||||
return metric.labels(**labels) if labels else metric
|
||||
|
||||
def process_request(self, request):
|
||||
transport = self._transport(request)
|
||||
method = self._method(request)
|
||||
self.label_metric(self.metrics.requests_by_method, request, method=method).inc()
|
||||
self.label_metric(self.metrics.requests_by_transport, request, transport=transport).inc()
|
||||
|
||||
# Mimic the behaviour of the deprecated "Request.is_ajax()" method.
|
||||
if request.headers.get("x-requested-with") == "XMLHttpRequest":
|
||||
self.label_metric(self.metrics.requests_ajax, request).inc()
|
||||
|
||||
content_length = int(request.headers.get("content-length") or 0)
|
||||
self.label_metric(self.metrics.requests_body_bytes, request).observe(content_length)
|
||||
request.prometheus_after_middleware_event = Time()
|
||||
|
||||
def _get_view_name(self, request):
|
||||
view_name = "<unnamed view>"
|
||||
if hasattr(request, "resolver_match"):
|
||||
if request.resolver_match is not None:
|
||||
if request.resolver_match.view_name is not None:
|
||||
view_name = request.resolver_match.view_name
|
||||
return view_name
|
||||
|
||||
def process_view(self, request, view_func, *view_args, **view_kwargs):
|
||||
transport = self._transport(request)
|
||||
method = self._method(request)
|
||||
if hasattr(request, "resolver_match"):
|
||||
name = request.resolver_match.view_name or "<unnamed view>"
|
||||
self.label_metric(
|
||||
self.metrics.requests_by_view_transport_method,
|
||||
request,
|
||||
view=name,
|
||||
transport=transport,
|
||||
method=method,
|
||||
).inc()
|
||||
|
||||
def process_template_response(self, request, response):
|
||||
if hasattr(response, "template_name"):
|
||||
self.label_metric(
|
||||
self.metrics.responses_by_templatename,
|
||||
request,
|
||||
response=response,
|
||||
templatename=str(response.template_name),
|
||||
).inc()
|
||||
return response
|
||||
|
||||
def process_response(self, request, response):
|
||||
method = self._method(request)
|
||||
name = self._get_view_name(request)
|
||||
status = str(response.status_code)
|
||||
self.label_metric(self.metrics.responses_by_status, request, response, status=status).inc()
|
||||
self.label_metric(
|
||||
self.metrics.responses_by_status_view_method,
|
||||
request,
|
||||
response,
|
||||
status=status,
|
||||
view=name,
|
||||
method=method,
|
||||
).inc()
|
||||
if hasattr(response, "charset"):
|
||||
self.label_metric(
|
||||
self.metrics.responses_by_charset,
|
||||
request,
|
||||
response,
|
||||
charset=str(response.charset),
|
||||
).inc()
|
||||
if hasattr(response, "streaming") and response.streaming:
|
||||
self.label_metric(self.metrics.responses_streaming, request, response).inc()
|
||||
if hasattr(response, "content"):
|
||||
self.label_metric(self.metrics.responses_body_bytes, request, response).observe(len(response.content))
|
||||
if hasattr(request, "prometheus_after_middleware_event"):
|
||||
self.label_metric(
|
||||
self.metrics.requests_latency_by_view_method,
|
||||
request,
|
||||
response,
|
||||
view=self._get_view_name(request),
|
||||
method=request.method,
|
||||
).observe(TimeSince(request.prometheus_after_middleware_event))
|
||||
else:
|
||||
self.label_metric(self.metrics.requests_unknown_latency, request, response).inc()
|
||||
return response
|
||||
|
||||
def process_exception(self, request, exception):
|
||||
self.label_metric(self.metrics.exceptions_by_type, request, type=type(exception).__name__).inc()
|
||||
if hasattr(request, "resolver_match"):
|
||||
name = request.resolver_match.view_name or "<unnamed view>"
|
||||
self.label_metric(self.metrics.exceptions_by_view, request, view=name).inc()
|
||||
if hasattr(request, "prometheus_after_middleware_event"):
|
||||
self.label_metric(
|
||||
self.metrics.requests_latency_by_view_method,
|
||||
request,
|
||||
view=self._get_view_name(request),
|
||||
method=request.method,
|
||||
).observe(TimeSince(request.prometheus_after_middleware_event))
|
||||
else:
|
||||
self.label_metric(self.metrics.requests_unknown_latency, request).inc()
|
||||
Reference in New Issue
Block a user