Jeremy Stretch 6 месяцев назад
Родитель
Сommit
ae55eed98f

+ 2 - 0
docs/integrations/prometheus-metrics.md

@@ -11,6 +11,8 @@ NetBox makes use of the [django-prometheus](https://github.com/korfuri/django-pr
 - Per model insert, update, and delete counters
 - Per view request counters
 - Per view request latency histograms
+- REST API requests (by endpoint & method)
+- GraphQL API requests
 - Request body size histograms
 - Response body size histograms
 - Response code counters

+ 40 - 0
netbox/netbox/metrics.py

@@ -0,0 +1,40 @@
+from django_prometheus.conf import NAMESPACE
+from django_prometheus import middleware
+from prometheus_client import Counter
+
+__all__ = (
+    'Metrics',
+)
+
+
+class Metrics(middleware.Metrics):
+    """
+    Expand the stock Metrics class from django_prometheus to add our own counters.
+    """
+
+    def register(self):
+        super().register()
+
+        # REST API metrics
+        self.rest_api_requests = self.register_metric(
+            Counter,
+            "rest_api_requests_total_by_method",
+            "Count of total REST API requests by method",
+            ["method"],
+            namespace=NAMESPACE,
+        )
+        self.rest_api_requests_by_view_method = self.register_metric(
+            Counter,
+            "rest_api_requests_total_by_view_method",
+            "Count of REST API requests by view & method",
+            ["view", "method"],
+            namespace=NAMESPACE,
+        )
+
+        # GraphQL API metrics
+        self.graphql_api_requests = self.register_metric(
+            Counter,
+            "graphql_api_requests_total",
+            "Count of total GraphQL API requests",
+            namespace=NAMESPACE,
+        )

+ 29 - 1
netbox/netbox/middleware.py

@@ -8,16 +8,20 @@ from django.core.exceptions import ImproperlyConfigured
 from django.db import connection, ProgrammingError
 from django.db.utils import InternalError
 from django.http import Http404, HttpResponseRedirect
+from django_prometheus import middleware
 
 from netbox.config import clear_config, get_config
+from netbox.metrics import Metrics
 from netbox.views import handler_500
-from utilities.api import is_api_request
+from utilities.api import is_api_request, is_graphql_request
 from utilities.error_handlers import handle_rest_api_exception
 from utilities.request import apply_request_processors
 
 __all__ = (
     'CoreMiddleware',
     'MaintenanceModeMiddleware',
+    'PrometheusAfterMiddleware',
+    'PrometheusBeforeMiddleware',
     'RemoteUserMiddleware',
 )
 
@@ -180,6 +184,30 @@ class RemoteUserMiddleware(RemoteUserMiddleware_):
         return groups
 
 
+class PrometheusBeforeMiddleware(middleware.PrometheusBeforeMiddleware):
+    metrics_cls = Metrics
+
+
+class PrometheusAfterMiddleware(middleware.PrometheusAfterMiddleware):
+    metrics_cls = Metrics
+
+    def process_response(self, request, response):
+        response = super().process_response(request, response)
+
+        # Increment REST API request counters
+        if is_api_request(request):
+            method = self._method(request)
+            name = self._get_view_name(request)
+            self.label_metric(self.metrics.rest_api_requests, request, method=method).inc()
+            self.label_metric(self.metrics.rest_api_requests_by_view_method, request, method=method, view=name).inc()
+
+        # Increment GraphQL API request counters
+        elif is_graphql_request(request):
+            self.metrics.graphql_api_requests.inc()
+
+        return response
+
+
 class MaintenanceModeMiddleware:
     """
     Middleware that checks if the application is in maintenance mode

+ 2 - 2
netbox/netbox/settings.py

@@ -472,9 +472,9 @@ if DEBUG:
 if METRICS_ENABLED:
     # If metrics are enabled, add the before & after Prometheus middleware
     MIDDLEWARE = [
-        'django_prometheus.middleware.PrometheusBeforeMiddleware',
+        'netbox.middleware.PrometheusBeforeMiddleware',
         *MIDDLEWARE,
-        'django_prometheus.middleware.PrometheusAfterMiddleware',
+        'netbox.middleware.PrometheusAfterMiddleware',
     ]
 
 # URLs

+ 8 - 0
netbox/utilities/api.py

@@ -23,6 +23,7 @@ __all__ = (
     'get_serializer_for_model',
     'get_view_name',
     'is_api_request',
+    'is_graphql_request',
 )
 
 
@@ -60,6 +61,13 @@ def is_api_request(request):
     return request.path_info.startswith(api_path) and request.content_type == HTTP_CONTENT_TYPE_JSON
 
 
+def is_graphql_request(request):
+    """
+    Return True of the request is being made via the GraphQL API.
+    """
+    return request.path_info == reverse('graphql') and request.content_type == HTTP_CONTENT_TYPE_JSON
+
+
 def get_view_name(view):
     """
     Derive the view name from its associated model, if it has one. Fall back to DRF's built-in `get_view_name()`.