Skip to content

Commit d8c8559

Browse files
authored
feat: Prometheus and HealthCheck (#1780)
1 parent 7f45104 commit d8c8559

12 files changed

Lines changed: 331 additions & 35 deletions

File tree

backend/package-lock.json

Lines changed: 220 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,21 @@
2929
"@nestjs/platform-express": "^10.0.0",
3030
"@nestjs/schematics": "^10.0.0",
3131
"@nestjs/swagger": "^7.0.3",
32+
"@nestjs/terminus": "^10.2.1",
3233
"@nestjs/testing": "^10.0.0",
3334
"@prisma/client": "^5.7.0",
3435
"dotenv": "^16.0.1",
36+
"express-prom-bundle": "^7.0.0",
37+
"helmet": "^7.0.0",
38+
"nest-winston": "^1.9.4",
3539
"nestjs-prisma": "^0.22.0",
3640
"pg": "^8.11.3",
41+
"prom-client": "^15.1.0",
3742
"reflect-metadata": "^0.2.0",
3843
"rimraf": "^5.0.0",
3944
"rxjs": "^7.8.0",
4045
"swagger-ui-express": "^5.0.0",
41-
"winston": "^3.11.0",
42-
"nest-winston": "^1.9.4",
43-
"helmet": "^7.0.0"
46+
"winston": "^3.11.0"
4447
},
4548
"devDependencies": {
4649
"@types/express": "^4.17.15",

backend/prisma/schema.prisma

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
generator client {
22
provider = "prisma-client-js"
3+
previewFeatures = ["metrics"]
34
}
45

56
datasource db {

backend/src/app.module.ts

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import "dotenv/config";
2-
import { Logger, MiddlewareConsumer, Module } from "@nestjs/common";
2+
import { Logger, MiddlewareConsumer, Module, RequestMethod } from "@nestjs/common";
33
import { HTTPLoggerMiddleware } from "./middleware/req.res.logger";
44
import { loggingMiddleware, PrismaModule } from "nestjs-prisma";
55
import { ConfigModule } from "@nestjs/config";
66
import { UsersModule } from "./users/users.module";
77
import { AppService } from "./app.service";
88
import { AppController } from "./app.controller";
9+
import { MetricsController } from "./metrics.controller";
10+
import { TerminusModule } from '@nestjs/terminus';
11+
import { HealthController } from "./health.controller";
912

1013
const DB_HOST = process.env.POSTGRES_HOST || "localhost";
1114
const DB_USER = process.env.POSTGRES_USER || "postgres";
@@ -14,33 +17,41 @@ const DB_PORT = process.env.POSTGRES_PORT || 5432;
1417
const DB_NAME = process.env.POSTGRES_DATABASE || "postgres";
1518
const DB_SCHEMA = process.env.DB_SCHEMA || "users";
1619

20+
function getMiddlewares() {
21+
if (process.env.PRISMA_LOGGING) {
22+
return [
23+
// configure your prisma middleware
24+
loggingMiddleware({
25+
logger: new Logger("PrismaMiddleware"),
26+
logLevel: "debug"
27+
})
28+
];
29+
}
30+
return [];
31+
}
32+
1733
@Module({
1834
imports: [
1935
ConfigModule.forRoot(),
36+
TerminusModule,
2037
PrismaModule.forRoot({
2138
isGlobal: true,
2239
prismaServiceOptions:{
2340
prismaOptions:{
24-
log: ["query", "info", "error", "warn"],
41+
log: ["error", "warn"],
2542
errorFormat: "pretty",
2643
datasourceUrl: `postgresql://${DB_USER}:${DB_PWD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?schema=${DB_SCHEMA}&connection_limit=5`,
2744
},
28-
middlewares: [
29-
// configure your prisma middleware
30-
loggingMiddleware({
31-
logger: new Logger("PrismaMiddleware"),
32-
logLevel: "log"
33-
})
34-
]
45+
middlewares: getMiddlewares(),
3546
},
3647
}),
3748
UsersModule
3849
],
39-
controllers: [AppController],
50+
controllers: [AppController,MetricsController, HealthController],
4051
providers: [AppService]
4152
})
4253
export class AppModule { // let's add a middleware on all routes
4354
configure(consumer: MiddlewareConsumer) {
44-
consumer.apply(HTTPLoggerMiddleware).forRoutes("*");
55+
consumer.apply(HTTPLoggerMiddleware).exclude({ path: 'metrics', method: RequestMethod.ALL }, { path: 'health', method: RequestMethod.ALL }).forRoutes('*');
4556
}
4657
}

backend/src/app.spec.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
import {NestExpressApplication} from '@nestjs/platform-express';
22
import {bootstrap} from "./app";
3-
3+
jest.mock('prom-client', () => ({
4+
Registry: jest.fn().mockImplementation(() => ({
5+
})),
6+
collectDefaultMetrics: jest.fn().mockImplementation(() => ({
7+
})),
8+
}));
9+
jest.mock('express-prom-bundle', () => ({
10+
default: jest.fn().mockImplementation(() => ({
11+
})),
12+
}));
13+
jest.mock('./prom', () => ({
14+
metricsMiddleware: jest.fn().mockImplementation((req, res, next) => next()),
15+
}));
416
describe('main', () => {
517
let app: NestExpressApplication;
618

backend/src/app.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { customLogger } from './common/logger.config';
55
import { NestExpressApplication } from '@nestjs/platform-express';
66
import helmet from 'helmet';
77
import { VersioningType } from '@nestjs/common';
8+
import { metricsMiddleware } from "./prom";
89

910
/**
1011
*
@@ -17,6 +18,7 @@ export async function bootstrap() {
1718
app.use(helmet());
1819
app.enableCors();
1920
app.set("trust proxy", 1);
21+
app.use(metricsMiddleware);
2022
app.enableShutdownHooks();
2123
app.setGlobalPrefix("api");
2224
app.enableVersioning({

backend/src/health.controller.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Controller, Get } from "@nestjs/common";
2+
import { HealthCheckService, HealthCheck, PrismaHealthIndicator } from "@nestjs/terminus";
3+
import { PrismaService } from "nestjs-prisma";
4+
@Controller("health")
5+
export class HealthController {
6+
constructor(
7+
private health: HealthCheckService,
8+
private prisma: PrismaHealthIndicator,
9+
private readonly prismaService: PrismaService,
10+
) {}
11+
12+
@Get()
13+
@HealthCheck()
14+
check() {
15+
return this.health.check([
16+
() => this.prisma.pingCheck('prisma', this.prismaService),
17+
]);
18+
}
19+
}

backend/src/metrics.controller.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Controller, Get, Res } from "@nestjs/common";
2+
import { Response } from "express";
3+
import { register } from "./prom";
4+
import { PrismaService } from "nestjs-prisma";
5+
@Controller("metrics")
6+
export class MetricsController {
7+
constructor(private prisma: PrismaService) {}
8+
9+
@Get()
10+
async getMetrics(@Res() res: Response) {
11+
const prismaMetrics = await this.prisma.$metrics.prometheus();
12+
const appMetrics = await register.metrics();
13+
res.end(prismaMetrics + appMetrics);
14+
}
15+
}

backend/src/middleware/req.res.logger.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export class HTTPLoggerMiddleware implements NestMiddleware {
1010

1111
response.on("finish", () => {
1212
const { statusCode } = response;
13-
const contentLength = response.get("content-length");
13+
const contentLength = response.get("content-length") || '-';
1414
const hostedHttpLogFormat = `${method} ${originalUrl} ${statusCode} ${contentLength} - ${request.get(
1515
"user-agent"
1616
)}`;

backend/src/prom.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import * as prom from 'prom-client';
2+
import promBundle from 'express-prom-bundle';
3+
const register = new prom.Registry();
4+
prom.collectDefaultMetrics({ register });
5+
const metricsMiddleware = promBundle({
6+
includeMethod: true,
7+
includePath: true,
8+
metricsPath: '/prom-metrics',
9+
promRegistry: register,
10+
});
11+
export { metricsMiddleware, register };

0 commit comments

Comments
 (0)