♻️ refactor: migrate 6 backends to shared-nestjs-metrics

Replace local MetricsService implementations with @manacore/shared-nestjs-metrics:
- chat, calendar, todo, clock, contacts, skilltree

Removes ~350 LOC of duplicated metrics code:
- Delete local metrics directories (service, module, controller)
- Remove manual metrics middleware from main.ts
- Use MetricsModule.register({ prefix: 'app_' }) pattern

Part of consolidation effort - see docs/CONSOLIDATION_OPPORTUNITIES.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Till-JS 2026-01-29 16:23:32 +01:00
parent f0adea04fd
commit a1ca002081
43 changed files with 54 additions and 709 deletions

View file

@ -18,6 +18,7 @@
},
"dependencies": {
"@manacore/shared-nestjs-auth": "workspace:*",
"@manacore/shared-nestjs-metrics": "workspace:*",
"@nestjs/common": "^10.4.9",
"@nestjs/config": "^3.3.0",
"@nestjs/core": "^10.4.9",

View file

@ -1,8 +1,8 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { MetricsModule } from '@manacore/shared-nestjs-metrics';
import { DatabaseModule } from './db/database.module';
import { HealthModule } from './health/health.module';
import { MetricsModule } from './metrics';
import { SkillModule } from './skill/skill.module';
import { ActivityModule } from './activity/activity.module';
@ -12,7 +12,10 @@ import { ActivityModule } from './activity/activity.module';
isGlobal: true,
envFilePath: '.env',
}),
MetricsModule,
MetricsModule.register({
prefix: 'skilltree_',
excludePaths: ['/health'],
}),
DatabaseModule,
HealthModule,
SkillModule,

View file

@ -1,49 +1,11 @@
import { NestFactory } from '@nestjs/core';
import { ValidationPipe, Logger } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { AppModule } from './app.module';
import { MetricsService } from './metrics/metrics.service';
// Normalize route paths to prevent high cardinality
function normalizeRoute(path: string): string {
return path
.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, ':id')
.replace(/\/\d+/g, '/:id');
}
async function bootstrap() {
const logger = new Logger('Bootstrap');
const app = await NestFactory.create(AppModule);
// Get MetricsService for request tracking
const metricsService = app.get(MetricsService);
// Global Express middleware to track ALL HTTP requests
app.use((req: Request, res: Response, next: NextFunction) => {
if (req.path === '/metrics') {
return next();
}
const startTime = Date.now();
const method = req.method;
const route = normalizeRoute(req.path);
res.once('finish', () => {
const duration = (Date.now() - startTime) / 1000;
metricsService.httpRequestsTotal.inc({
method,
route,
status: res.statusCode.toString(),
});
metricsService.httpRequestDuration.observe(
{ method, route, status: res.statusCode.toString() },
duration
);
});
next();
});
// Enable CORS
app.enableCors({
origin: (origin, callback) => {

View file

@ -1,3 +0,0 @@
export * from './metrics.module';
export * from './metrics.service';
export * from './metrics.controller';

View file

@ -1,13 +0,0 @@
import { Controller, Get, Header } from '@nestjs/common';
import { MetricsService } from './metrics.service';
@Controller('metrics')
export class MetricsController {
constructor(private metricsService: MetricsService) {}
@Get()
@Header('Content-Type', 'text/plain')
async getMetrics(): Promise<string> {
return this.metricsService.getMetrics();
}
}

View file

@ -1,11 +0,0 @@
import { Module, Global } from '@nestjs/common';
import { MetricsController } from './metrics.controller';
import { MetricsService } from './metrics.service';
@Global()
@Module({
controllers: [MetricsController],
providers: [MetricsService],
exports: [MetricsService],
})
export class MetricsModule {}

View file

@ -1,37 +0,0 @@
import { Injectable, OnModuleInit } from '@nestjs/common';
import { collectDefaultMetrics, Counter, Histogram, Registry } from 'prom-client';
@Injectable()
export class MetricsService implements OnModuleInit {
private registry: Registry;
public httpRequestsTotal: Counter;
public httpRequestDuration: Histogram;
constructor() {
this.registry = new Registry();
this.httpRequestsTotal = new Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status'],
registers: [this.registry],
});
this.httpRequestDuration = new Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status'],
buckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5],
registers: [this.registry],
});
}
onModuleInit() {
collectDefaultMetrics({ register: this.registry });
}
async getMetrics(): Promise<string> {
return this.registry.metrics();
}
}