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 model insert, update, and delete counters
 - Per view request counters
 - Per view request counters
 - Per view request latency histograms
 - Per view request latency histograms
+- REST API requests (by endpoint & method)
+- GraphQL API requests
 - Request body size histograms
 - Request body size histograms
 - Response body size histograms
 - Response body size histograms
 - Response code counters
 - 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 import connection, ProgrammingError
 from django.db.utils import InternalError
 from django.db.utils import InternalError
 from django.http import Http404, HttpResponseRedirect
 from django.http import Http404, HttpResponseRedirect
+from django_prometheus import middleware
 
 
 from netbox.config import clear_config, get_config
 from netbox.config import clear_config, get_config
+from netbox.metrics import Metrics
 from netbox.views import handler_500
 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.error_handlers import handle_rest_api_exception
 from utilities.request import apply_request_processors
 from utilities.request import apply_request_processors
 
 
 __all__ = (
 __all__ = (
     'CoreMiddleware',
     'CoreMiddleware',
     'MaintenanceModeMiddleware',
     'MaintenanceModeMiddleware',
+    'PrometheusAfterMiddleware',
+    'PrometheusBeforeMiddleware',
     'RemoteUserMiddleware',
     'RemoteUserMiddleware',
 )
 )
 
 
@@ -180,6 +184,30 @@ class RemoteUserMiddleware(RemoteUserMiddleware_):
         return groups
         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:
 class MaintenanceModeMiddleware:
     """
     """
     Middleware that checks if the application is in maintenance mode
     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_ENABLED:
     # If metrics are enabled, add the before & after Prometheus middleware
     # If metrics are enabled, add the before & after Prometheus middleware
     MIDDLEWARE = [
     MIDDLEWARE = [
-        'django_prometheus.middleware.PrometheusBeforeMiddleware',
+        'netbox.middleware.PrometheusBeforeMiddleware',
         *MIDDLEWARE,
         *MIDDLEWARE,
-        'django_prometheus.middleware.PrometheusAfterMiddleware',
+        'netbox.middleware.PrometheusAfterMiddleware',
     ]
     ]
 
 
 # URLs
 # URLs

+ 8 - 0
netbox/utilities/api.py

@@ -23,6 +23,7 @@ __all__ = (
     'get_serializer_for_model',
     'get_serializer_for_model',
     'get_view_name',
     'get_view_name',
     'is_api_request',
     '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
     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):
 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()`.
     Derive the view name from its associated model, if it has one. Fall back to DRF's built-in `get_view_name()`.