diff --git a/backend/src/api/middleware/sep24-metrics.middleware.ts b/backend/src/api/middleware/sep24-metrics.middleware.ts new file mode 100644 index 0000000..eaf2b5a --- /dev/null +++ b/backend/src/api/middleware/sep24-metrics.middleware.ts @@ -0,0 +1,23 @@ +import { Request, Response, NextFunction } from 'express'; +import { recordSep24InteractionRequest } from '../../services/sep24-metrics.service'; + +/** + * Middleware that records SEP-24 interactive endpoint duration and request counts + * to Prometheus. + */ +export function sep24MetricsMiddleware( + req: Request, + res: Response, + next: NextFunction, +): void { + const start = Date.now(); + const method = req.method; + const endpoint = req.route?.path ?? req.path; + + res.on('finish', () => { + const durationSeconds = (Date.now() - start) / 1000; + recordSep24InteractionRequest(endpoint, method, res.statusCode, durationSeconds); + }); + + next(); +} diff --git a/backend/src/api/routes/sep24.route.ts b/backend/src/api/routes/sep24.route.ts index 19f01c2..f8f9b80 100644 --- a/backend/src/api/routes/sep24.route.ts +++ b/backend/src/api/routes/sep24.route.ts @@ -9,10 +9,12 @@ import { } from '../../services/kyc.service'; import prisma from '../../lib/prisma'; import { isValidStellarPublicKey } from '../../utils/stellar-address'; -import { Sep24Service } from '../../services/sep24.service'; +import { sep24MetricsMiddleware } from '../middleware/sep24-metrics.middleware'; const router = Router(); +router.use(sep24MetricsMiddleware); + interface InteractiveRequest { asset_code: string; account?: string; diff --git a/backend/src/services/sep24-metrics.service.test.ts b/backend/src/services/sep24-metrics.service.test.ts new file mode 100644 index 0000000..2aead23 --- /dev/null +++ b/backend/src/services/sep24-metrics.service.test.ts @@ -0,0 +1,31 @@ +import { metricsService } from './metrics.service'; +import { recordSep24InteractionRequest } from './sep24-metrics.service'; + +describe('SEP-24 metrics', () => { + beforeEach(() => { + metricsService.reset(); + }); + + it('registers interaction request counter and duration histogram', async () => { + const metrics = await metricsService.getMetrics(); + + expect(metrics).toContain('sep24_interaction_requests_total'); + expect(metrics).toContain('sep24_interaction_endpoint_duration_seconds'); + }); + + it('records interaction endpoint duration and request count', async () => { + recordSep24InteractionRequest( + '/transactions/deposit/interactive', + 'POST', + 200, + 0.125, + ); + + const metrics = await metricsService.getMetrics(); + + expect(metrics).toContain('endpoint="/transactions/deposit/interactive"'); + expect(metrics).toContain('method="POST"'); + expect(metrics).toContain('status_code="200"'); + expect(metrics).toContain('sep24_interaction_endpoint_duration_seconds_count'); + }); +}); diff --git a/backend/src/services/sep24-metrics.service.ts b/backend/src/services/sep24-metrics.service.ts new file mode 100644 index 0000000..c62780c --- /dev/null +++ b/backend/src/services/sep24-metrics.service.ts @@ -0,0 +1,33 @@ +import promClient, { Counter, Histogram } from 'prom-client'; +import { metricsService } from './metrics.service'; + +const sep24InteractionRequestsTotal = new Counter({ + name: 'sep24_interaction_requests_total', + help: 'Total number of SEP-24 interactive endpoint requests', + labelNames: ['endpoint', 'method', 'status_code'] as const, + registers: [metricsService.getRegistry()], +}); + +const sep24InteractionEndpointDuration = new Histogram({ + name: 'sep24_interaction_endpoint_duration_seconds', + help: 'Duration of SEP-24 interactive endpoint requests in seconds', + labelNames: ['endpoint', 'method', 'status_code'] as const, + buckets: [0.01, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10], + registers: [metricsService.getRegistry()], +}); + +export function recordSep24InteractionRequest( + endpoint: string, + method: string, + statusCode: number, + durationSeconds: number, +): void { + const labels = { + endpoint, + method, + status_code: String(statusCode), + }; + + sep24InteractionRequestsTotal.inc(labels); + sep24InteractionEndpointDuration.observe(labels, durationSeconds); +}