mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-20 11:23:38 +02:00
✨ feat(stats): add user statistics to Prometheus metrics and Grafana
- Add user metrics to mana-core-auth MetricsService: - auth_users_total: Total registered users - auth_users_verified: Email-verified users - auth_users_created_today/this_week/this_month - Create Grafana user-statistics dashboard with: - User overview stats (total, verified, verification rate, new today) - Registration period breakdown (today/week/month) - User growth trends over time - Enhance telegram-stats-bot /users command: - Add yesterday comparison with trends - Add week-over-week comparison - Add mini bar chart for last 7 days registration - Include user stats in daily Telegram report
This commit is contained in:
parent
9fedb7cfdd
commit
0cd2bc858a
6 changed files with 798 additions and 13 deletions
|
|
@ -1,15 +1,28 @@
|
|||
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||
import { Injectable, Logger, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import * as client from 'prom-client';
|
||||
import { count, eq, gte, and, isNull, sql } from 'drizzle-orm';
|
||||
import { getDb } from '../db/connection';
|
||||
import { users } from '../db/schema';
|
||||
|
||||
@Injectable()
|
||||
export class MetricsService implements OnModuleInit {
|
||||
export class MetricsService implements OnModuleInit, OnModuleDestroy {
|
||||
private readonly logger = new Logger(MetricsService.name);
|
||||
private readonly register: client.Registry;
|
||||
private updateInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
// HTTP metrics
|
||||
readonly httpRequestsTotal: client.Counter<string>;
|
||||
readonly httpRequestDuration: client.Histogram<string>;
|
||||
|
||||
constructor() {
|
||||
// User metrics
|
||||
readonly usersTotal: client.Gauge<string>;
|
||||
readonly usersVerified: client.Gauge<string>;
|
||||
readonly usersCreatedToday: client.Gauge<string>;
|
||||
readonly usersCreatedThisWeek: client.Gauge<string>;
|
||||
readonly usersCreatedThisMonth: client.Gauge<string>;
|
||||
|
||||
constructor(private readonly configService: ConfigService) {
|
||||
this.register = new client.Registry();
|
||||
|
||||
// Add default metrics (CPU, memory, event loop, etc.)
|
||||
|
|
@ -34,10 +47,113 @@ export class MetricsService implements OnModuleInit {
|
|||
buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 2, 5],
|
||||
registers: [this.register],
|
||||
});
|
||||
|
||||
// User statistics gauges
|
||||
this.usersTotal = new client.Gauge({
|
||||
name: 'auth_users_total',
|
||||
help: 'Total number of registered users',
|
||||
registers: [this.register],
|
||||
});
|
||||
|
||||
this.usersVerified = new client.Gauge({
|
||||
name: 'auth_users_verified',
|
||||
help: 'Number of email-verified users',
|
||||
registers: [this.register],
|
||||
});
|
||||
|
||||
this.usersCreatedToday = new client.Gauge({
|
||||
name: 'auth_users_created_today',
|
||||
help: 'Number of users created today',
|
||||
registers: [this.register],
|
||||
});
|
||||
|
||||
this.usersCreatedThisWeek = new client.Gauge({
|
||||
name: 'auth_users_created_this_week',
|
||||
help: 'Number of users created this week',
|
||||
registers: [this.register],
|
||||
});
|
||||
|
||||
this.usersCreatedThisMonth = new client.Gauge({
|
||||
name: 'auth_users_created_this_month',
|
||||
help: 'Number of users created this month',
|
||||
registers: [this.register],
|
||||
});
|
||||
}
|
||||
|
||||
onModuleInit() {
|
||||
// Metrics are ready
|
||||
async onModuleInit() {
|
||||
// Update user metrics immediately and then every 60 seconds
|
||||
await this.updateUserMetrics();
|
||||
this.updateInterval = setInterval(() => this.updateUserMetrics(), 60000);
|
||||
}
|
||||
|
||||
onModuleDestroy() {
|
||||
if (this.updateInterval) {
|
||||
clearInterval(this.updateInterval);
|
||||
this.updateInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async updateUserMetrics() {
|
||||
const databaseUrl = this.configService.get<string>('DATABASE_URL');
|
||||
if (!databaseUrl) {
|
||||
this.logger.warn('DATABASE_URL not configured, user metrics unavailable');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const db = getDb(databaseUrl);
|
||||
const now = new Date();
|
||||
|
||||
// Start of today (midnight)
|
||||
const startOfToday = new Date(now);
|
||||
startOfToday.setHours(0, 0, 0, 0);
|
||||
|
||||
// Start of week (Monday)
|
||||
const startOfWeek = new Date(now);
|
||||
const day = startOfWeek.getDay();
|
||||
const diff = startOfWeek.getDate() - day + (day === 0 ? -6 : 1);
|
||||
startOfWeek.setDate(diff);
|
||||
startOfWeek.setHours(0, 0, 0, 0);
|
||||
|
||||
// Start of month
|
||||
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
|
||||
// Query all metrics in parallel
|
||||
const [totalResult, verifiedResult, todayResult, weekResult, monthResult] = await Promise.all(
|
||||
[
|
||||
// Total users
|
||||
db.select({ count: count() }).from(users).where(isNull(users.deletedAt)),
|
||||
// Verified users
|
||||
db
|
||||
.select({ count: count() })
|
||||
.from(users)
|
||||
.where(and(isNull(users.deletedAt), eq(users.emailVerified, true))),
|
||||
// Users created today
|
||||
db
|
||||
.select({ count: count() })
|
||||
.from(users)
|
||||
.where(and(isNull(users.deletedAt), gte(users.createdAt, startOfToday))),
|
||||
// Users created this week
|
||||
db
|
||||
.select({ count: count() })
|
||||
.from(users)
|
||||
.where(and(isNull(users.deletedAt), gte(users.createdAt, startOfWeek))),
|
||||
// Users created this month
|
||||
db
|
||||
.select({ count: count() })
|
||||
.from(users)
|
||||
.where(and(isNull(users.deletedAt), gte(users.createdAt, startOfMonth))),
|
||||
]
|
||||
);
|
||||
|
||||
this.usersTotal.set(totalResult[0].count);
|
||||
this.usersVerified.set(verifiedResult[0].count);
|
||||
this.usersCreatedToday.set(todayResult[0].count);
|
||||
this.usersCreatedThisWeek.set(weekResult[0].count);
|
||||
this.usersCreatedThisMonth.set(monthResult[0].count);
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to update user metrics:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async getMetrics(): Promise<string> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue