From 938e2912686f26f79cee4e2177368e91984c5fae Mon Sep 17 00:00:00 2001 From: Ruslan Bakiev <572431+veikab@users.noreply.github.com> Date: Tue, 3 Feb 2026 10:32:44 +0700 Subject: [PATCH] Add Prometheus metrics endpoint for KYC monitoring --- kyc/urls.py | 5 +++-- kyc_app/views.py | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/kyc/urls.py b/kyc/urls.py index 79e4284..5d52e18 100644 --- a/kyc/urls.py +++ b/kyc/urls.py @@ -1,7 +1,7 @@ from django.contrib import admin from django.urls import path from django.views.decorators.csrf import csrf_exempt -from kyc_app.views import UserGraphQLView, M2MGraphQLView, PublicGraphQLView +from kyc_app.views import UserGraphQLView, M2MGraphQLView, PublicGraphQLView, metrics_view from kyc_app.schemas.user_schema import user_schema from kyc_app.schemas.m2m_schema import m2m_schema from kyc_app.schemas.public_schema import public_schema @@ -11,4 +11,5 @@ urlpatterns = [ path('graphql/user/', csrf_exempt(UserGraphQLView.as_view(graphiql=True, schema=user_schema))), path('graphql/m2m/', csrf_exempt(M2MGraphQLView.as_view(graphiql=True, schema=m2m_schema))), path('graphql/public/', csrf_exempt(PublicGraphQLView.as_view(graphiql=True, schema=public_schema))), -] \ No newline at end of file + path('metrics', metrics_view), +] diff --git a/kyc_app/views.py b/kyc_app/views.py index 04ef8aa..043768e 100644 --- a/kyc_app/views.py +++ b/kyc_app/views.py @@ -3,9 +3,16 @@ Views for KYC API. Authentication is handled by GRAPHENE MIDDLEWARE in settings.py """ +import os +from datetime import timedelta + +from django.db.models import Count, Q +from django.http import HttpResponse +from django.utils import timezone from graphene_django.views import GraphQLView from .graphql_middleware import UserJWTMiddleware +from .models import KYCProfile class UserGraphQLView(GraphQLView): @@ -47,3 +54,37 @@ class PublicGraphQLView(GraphQLView): # Use optional auth middleware that doesn't fail on missing token kwargs['middleware'] = [OptionalUserJWTMiddleware()] super().__init__(*args, **kwargs) + + +def metrics_view(request): + """Prometheus metrics endpoint for KYC monitoring status.""" + stale_days = int(os.getenv("KYC_STALE_DAYS", "90")) + cutoff = timezone.now() - timedelta(days=stale_days) + + stats = KYCProfile.objects.filter(workflow_status="active").aggregate( + total=Count("id"), + stale=Count("id", filter=Q(updated_at__lt=cutoff) | Q(updated_at__isnull=True)), + fresh=Count("id", filter=Q(updated_at__gte=cutoff)), + ) + + total = int(stats.get("total") or 0) + stale = int(stats.get("stale") or 0) + fresh = int(stats.get("fresh") or 0) + ratio = 0.0 if total == 0 else stale / total + + payload = ( + "# HELP kyc_monitoring_total Total active profiles in monitoring\n" + "# TYPE kyc_monitoring_total gauge\n" + f"kyc_monitoring_total {total}\n" + "# HELP kyc_monitoring_stale Stale profiles (no refresh within window)\n" + "# TYPE kyc_monitoring_stale gauge\n" + f"kyc_monitoring_stale {stale}\n" + "# HELP kyc_monitoring_fresh Fresh profiles (refreshed within window)\n" + "# TYPE kyc_monitoring_fresh gauge\n" + f"kyc_monitoring_fresh {fresh}\n" + "# HELP kyc_monitoring_stale_ratio Stale / total ratio\n" + "# TYPE kyc_monitoring_stale_ratio gauge\n" + f"kyc_monitoring_stale_ratio {ratio}\n" + ) + + return HttpResponse(payload, content_type="text/plain; version=0.0.4; charset=utf-8")