feat(admin/health): System Health Level 2 — метрики HTTP-запросов

backend/src/utils/metrics.js: лёгкие in-memory метрики (сброс при рестарте) —
всего запросов, req/min (скользящее окно), латентность avg/p50/p95/p99,
разбивка по статусам 2xx/3xx/4xx/5xx, топ маршрутов по частоте/латентности/
ошибкам (группировка по шаблону route.path, не по URL).

server.js: middleware (на /api, по res 'finish') пишет латентность и статус.
adminController.getMetrics + GET /api/admin/metrics (под admin-auth).

admin.js: health-страница переведена на refreshHealth/renderHealth (Level 1)
+ секция «Метрики запросов»: карточки req/min/всего/avg/p95/p99/5xx, цветная
полоса статусов, топ медленных/частых/ошибочных маршрутов.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-30 18:27:58 +03:00
parent f7d27ecb91
commit 4a424505a8
5 changed files with 224 additions and 42 deletions
+13
View File
@@ -138,6 +138,19 @@ const { requireFeature } = require('./middleware/features');
app.use('/api/classroom', rateLimit({ windowMs: 60_000, max: 6000, message: 'Слишком много запросов' }));
app.use('/api', rateLimit({ windowMs: 60_000, max: 600, message: 'Слишком много запросов, подождите минуту' }));
/* ── Request metrics (System Health Level 2) ── */
const metrics = require('./utils/metrics');
app.use((req, res, next) => {
if (!req.originalUrl.startsWith('/api')) return next();
const start = process.hrtime.bigint();
res.on('finish', () => {
const ms = Number(process.hrtime.bigint() - start) / 1e6;
const route = (req.baseUrl || '') + (req.route && req.route.path ? req.route.path : '');
metrics.record(req.method, route || req.path || '(unmatched)', res.statusCode, ms);
});
next();
});
/* ── Routes ── */
app.use('/api/auth', authRoutes);
app.use('/api/subjects', subjectRoutes);