Skip to content

Commit

Permalink
feat: nestjs metric exporter 기능 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
jcy0308 committed Sep 2, 2024
1 parent f529961 commit b0894b1
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 1 deletion.
47 changes: 47 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@prisma/client": "^5.7.1",
"@types/cron": "^2.4.0",
"@types/multer": "^1.4.11",
"@willsoto/nestjs-prometheus": "^6.0.1",
"bcrypt": "^5.1.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
Expand Down
2 changes: 2 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { TaskModule } from "./modules/task/task.module";
import { AchievementsModule } from "./modules/achievements/achievements.module";
import { ThesesModule } from "./modules/theses/theses.module";
import { KafkaModule } from "./config/kafka/kafka.module";
import { MetricsModule } from "./config/metrics/metrics.module";

@Module({
imports: [
Expand All @@ -32,6 +33,7 @@ import { KafkaModule } from "./config/kafka/kafka.module";
AchievementsModule,
ThesesModule,
KafkaModule,
MetricsModule,
],
controllers: [AppController],
providers: [AppService],
Expand Down
92 changes: 92 additions & 0 deletions src/config/metrics/interceptors/request.prom.interceptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { CallHandler, ExecutionContext, Injectable, NestInterceptor, OnModuleInit } from "@nestjs/common";
import { Counter, Gauge, Histogram } from "prom-client";
import { Observable, tap } from "rxjs";

@Injectable()
export class PrometheusInterceptor implements NestInterceptor, OnModuleInit {
onModuleInit() {
this.requestSuccessHistogram.reset();
this.requestFailHistogram.reset();
this.failureCounter.reset();
}
// status code 2XX
private readonly requestSuccessHistogram = new Histogram({
name: "nestjs_success_requests",
help: "NestJs success requests - duration in seconds",
labelNames: ["handler", "controller", "method"],
buckets: [0.0001, 0.001, 0.005, 0.01, 0.025, 0.05, 0.075, 0.09, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
});

// status code != 2XX
private readonly requestFailHistogram = new Histogram({
name: "nestjs_fail_requests",
help: "NestJs fail requests - duration in seconds",
labelNames: ["handler", "controller", "method"],
buckets: [0.0001, 0.001, 0.005, 0.01, 0.025, 0.05, 0.075, 0.09, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
});

private readonly failureCounter = new Counter({
name: "nestjs_requests_failed_count",
help: "NestJs requests that failed",
labelNames: ["handler", "controller", "error", "method"],
});

static registerServiceInfo(serviceInfo: { domain: string; name: string; version: string }): PrometheusInterceptor {
new Gauge({
name: "nestjs_info",
help: "NestJs service version info",
labelNames: ["domain", "name", "version"],
}).set(
{
domain: serviceInfo.domain,
name: `${serviceInfo.domain}.${serviceInfo.name}`,
version: serviceInfo.version,
},
1
);

return new PrometheusInterceptor();
}

// metrics url 요청은 트래킹 필요 x
private isAvailableMetricsUrl(url: string): boolean {
const excludePaths = "metrics";
if (url.includes(excludePaths)) {
return false;
}
return true;
}

intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const originUrl = context.switchToHttp().getRequest().url.toString();

const method = context.switchToHttp().getRequest().method.toString();
const labels = {
controller: context.getClass().name,
handler: context.getHandler().name,
method: method,
};

try {
const requestSuccessTimer = this.requestSuccessHistogram.startTimer(labels);
const requestFailTimer = this.requestFailHistogram.startTimer(labels);
return next.handle().pipe(
tap({
next: () => {
if (this.isAvailableMetricsUrl(originUrl)) {
requestSuccessTimer();
}
// Handle the next event here
},
error: () => {
if (this.isAvailableMetricsUrl(originUrl)) {
requestFailTimer();
this.failureCounter.labels({ ...labels }).inc(1);
}
// Handle the error event here
},
})
);
} catch (error) {}
}
}
22 changes: 22 additions & 0 deletions src/config/metrics/interceptors/response.prom.interceptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common";
import { defaultIfEmpty, map } from "rxjs";

@Injectable()
export class ResponseInterceptor implements NestInterceptor {
intercept(_context: ExecutionContext, next: CallHandler) {
const req: Request = _context.switchToHttp().getRequest();

const excludePaths = ["/api/capa-metrics", "/capa-metrics"];

return next
.handle()
.pipe(defaultIfEmpty(null))
.pipe(
map((result) => {
if (excludePaths.includes(req.url)) {
return result;
}
})
);
}
}
15 changes: 15 additions & 0 deletions src/config/metrics/metrics.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// metrics.module.ts
import { Module } from "@nestjs/common";
import { PrometheusModule as Prometheus } from "@willsoto/nestjs-prometheus";

@Module({
imports: [
Prometheus.register({
path: "/metrics",
defaultMetrics: {
enabled: true,
},
}),
],
})
export class MetricsModule {}
5 changes: 4 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { ValidationPipe, VersioningType } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
import { winstonLogger } from "./config/logger/winston/winston.config";
import { ResponseInterceptor } from "./config/metrics/interceptors/response.prom.interceptor";
import { PrometheusInterceptor } from "./config/metrics/interceptors/request.prom.interceptor";

async function bootstrap() {
const app = await NestFactory.create(AppModule, {
Expand All @@ -23,7 +25,8 @@ async function bootstrap() {
},
})
);

app.useGlobalInterceptors(new ResponseInterceptor());
app.useGlobalInterceptors(new PrometheusInterceptor());
app.enableCors({
origin: "*",
credentials: true,
Expand Down

0 comments on commit b0894b1

Please sign in to comment.