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:
@@ -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');
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user