Import source: app/backend/src/routes/metrics

Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
arsam
2026-09-18 23:20:18 +03:30
parent 2fc1b2517a
commit 7176e2096d
+63
View File
@@ -0,0 +1,63 @@
import type { FastifyInstance } from 'fastify';
import { redis } from '../redis.js';
import { prisma } from '../db.js';
import { requireAuth } from '../plugins/session.js';
export default async function metricsRoutes(app: FastifyInstance) {
app.get('/api/v1/metrics', { preHandler: requireAuth(['admin']) }, async (_req, reply) => {
reply.header('Content-Type', 'text/plain; version=0.0.4; charset=utf-8');
const lines: string[] = [];
const now = Date.now();
const uptimeSec = process.uptime();
lines.push(`# HELP teknav_uptime_seconds Process uptime in seconds`);
lines.push(`# TYPE teknav_uptime_seconds gauge`);
lines.push(`teknav_uptime_seconds ${uptimeSec.toFixed(2)}`);
lines.push(`# HELP teknav_nodejs_heap_used_bytes Node.js heap used`);
lines.push(`# TYPE teknav_nodejs_heap_used_bytes gauge`);
const mem = process.memoryUsage();
lines.push(`teknav_nodejs_heap_used_bytes ${mem.heapUsed}`);
lines.push(`# HELP teknav_nodejs_heap_total_bytes Node.js heap total`);
lines.push(`# TYPE teknav_nodejs_heap_total_bytes gauge`);
lines.push(`teknav_nodejs_heap_total_bytes ${mem.heapTotal}`);
lines.push(`# HELP teknav_nodejs_rss_bytes Node.js RSS`);
lines.push(`# TYPE teknav_nodejs_rss_bytes gauge`);
lines.push(`teknav_nodejs_rss_bytes ${mem.rss}`);
try {
const articleCount = await prisma.article.count({ where: { status: 'منتشرشده' } });
lines.push(`# HELP teknav_articles_published_total Total published articles`);
lines.push(`# TYPE teknav_articles_published_total gauge`);
lines.push(`teknav_articles_published_total ${articleCount}`);
const userCount = await prisma.user.count();
lines.push(`# HELP teknav_users_total Total users`);
lines.push(`# TYPE teknav_users_total gauge`);
lines.push(`teknav_users_total ${userCount}`);
const subscriberCount = await prisma.newsletterSubscriber.count();
lines.push(`# HELP teknav_newsletter_subscribers_total Newsletter subscribers`);
lines.push(`# TYPE teknav_newsletter_subscribers_total gauge`);
lines.push(`teknav_newsletter_subscribers_total ${subscriberCount}`);
} catch {
// DB unreachable — skip DB metrics
}
try {
const activeWindow = 5 * 60 * 1000;
const cutoff = now - activeWindow;
const guests = await redis.zremrangebyscore('visitors:guests', 0, cutoff).then(() => redis.zcard('visitors:guests'));
const users = await redis.zremrangebyscore('visitors:users', 0, cutoff).then(() => redis.zcard('visitors:users'));
lines.push(`# HELP teknav_active_visitors Current active visitors`);
lines.push(`# TYPE teknav_active_visitors gauge`);
lines.push(`teknav_active_visitors{type="guest"} ${guests}`);
lines.push(`teknav_active_visitors{type="user"} ${users}`);
} catch {
// Redis unreachable — skip
}
return reply.send(lines.join('\n') + '\n');
});
}