From 7bc52b8ca598ccc02c3572c0378fb2bb06858ceb Mon Sep 17 00:00:00 2001 From: Luna Simons Date: Wed, 8 Nov 2023 19:46:33 +0100 Subject: [PATCH] feat: added requests and request duration metrics --- package.json | 2 ++ src/app.module.ts | 2 ++ src/metrics/metrics.controller.spec.ts | 18 +++++++++++ src/metrics/metrics.controller.ts | 14 +++++++++ src/metrics/metrics.middleware.spec.ts | 7 +++++ src/metrics/metrics.middleware.ts | 21 +++++++++++++ src/metrics/metrics.module.ts | 43 ++++++++++++++++++++++++++ yarn.lock | 30 ++++++++++++++++++ 8 files changed, 137 insertions(+) create mode 100644 src/metrics/metrics.controller.spec.ts create mode 100644 src/metrics/metrics.controller.ts create mode 100644 src/metrics/metrics.middleware.spec.ts create mode 100644 src/metrics/metrics.middleware.ts create mode 100644 src/metrics/metrics.module.ts diff --git a/package.json b/package.json index 17207fb..9e3e3b9 100644 --- a/package.json +++ b/package.json @@ -27,12 +27,14 @@ "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "@nestjs/swagger": "^7.1.10", + "@willsoto/nestjs-prometheus": "^6.0.0", "cache-manager": "^5.2.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "helmet": "^7.0.0", "ics": "^3.4.0", "moment": "^2.29.4", + "prom-client": "^15.0.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "webuntis": "^2.0.3" diff --git a/src/app.module.ts b/src/app.module.ts index ff8fb26..5bdf90f 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -5,6 +5,7 @@ import { ConfigModule } from '@nestjs/config'; import { SubjectsModule } from './subjects/subjects.module'; import { LessonsModule } from './lessons/lessons.module'; import { HolidaysModule } from './holidays/holidays.module'; +import { MetricsModule } from './metrics/metrics.module'; @Module({ imports: [ @@ -14,6 +15,7 @@ import { HolidaysModule } from './holidays/holidays.module'; SubjectsModule, LessonsModule, HolidaysModule, + MetricsModule, ], }) export class AppModule {} diff --git a/src/metrics/metrics.controller.spec.ts b/src/metrics/metrics.controller.spec.ts new file mode 100644 index 0000000..bb34159 --- /dev/null +++ b/src/metrics/metrics.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { MetricsController } from './metrics.controller'; + +describe('MetricsController', () => { + let controller: MetricsController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [MetricsController], + }).compile(); + + controller = module.get(MetricsController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/metrics/metrics.controller.ts b/src/metrics/metrics.controller.ts new file mode 100644 index 0000000..db74af5 --- /dev/null +++ b/src/metrics/metrics.controller.ts @@ -0,0 +1,14 @@ +import { Controller, Get, Res } from '@nestjs/common'; +import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; +import { PrometheusController } from '@willsoto/nestjs-prometheus'; +import { Response } from 'express'; + +@ApiTags('metrics') +@Controller('metrics') +export class MetricsController extends PrometheusController { + @ApiOkResponse({ description: 'Returns metrics to be used by Prometheus.' }) + @Get() + getMetrics(@Res({ passthrough: true }) response: Response) { + return super.index(response); + } +} diff --git a/src/metrics/metrics.middleware.spec.ts b/src/metrics/metrics.middleware.spec.ts new file mode 100644 index 0000000..256c7b2 --- /dev/null +++ b/src/metrics/metrics.middleware.spec.ts @@ -0,0 +1,7 @@ +import { MetricsMiddleware } from './metrics.middleware'; + +describe('MetricsMiddleware', () => { + it('should be defined', () => { + expect(new MetricsMiddleware()).toBeDefined(); + }); +}); diff --git a/src/metrics/metrics.middleware.ts b/src/metrics/metrics.middleware.ts new file mode 100644 index 0000000..4d65efe --- /dev/null +++ b/src/metrics/metrics.middleware.ts @@ -0,0 +1,21 @@ +import { Injectable, NestMiddleware } from '@nestjs/common'; +import { InjectMetric } from '@willsoto/nestjs-prometheus'; +import { Request, Response } from 'express'; +import { Counter, Summary } from 'prom-client'; + +@Injectable() +export class MetricsMiddleware implements NestMiddleware { + constructor( + @InjectMetric('uis_request_count') + private readonly requestCountMetric: Counter, + @InjectMetric('uis_request_duration') + private readonly requestDurationMetric: Summary, + ) {} + + use(_req: Request, res: Response, next: () => void) { + this.requestCountMetric.inc(); + const end = this.requestDurationMetric.startTimer(); + res.on('finish', () => end()); + next(); + } +} diff --git a/src/metrics/metrics.module.ts b/src/metrics/metrics.module.ts new file mode 100644 index 0000000..1d8e9fb --- /dev/null +++ b/src/metrics/metrics.module.ts @@ -0,0 +1,43 @@ +import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; +import { + makeCounterProvider, + makeSummaryProvider, + PrometheusModule, +} from '@willsoto/nestjs-prometheus'; +import { ClassesController } from 'src/classes/classes.controller'; +import { HolidaysController } from 'src/holidays/holidays.controller'; +import { LessonsController } from 'src/lessons/lessons.controller'; +import { SubjectsController } from 'src/subjects/subjects.controller'; +import { MetricsController } from './metrics.controller'; +import { MetricsMiddleware } from './metrics.middleware'; + +@Module({ + imports: [ + PrometheusModule.register({ + controller: MetricsController, + }), + ], + controllers: [MetricsController], + providers: [ + makeCounterProvider({ + name: 'uis_request_count', + help: 'The amount of requests processed.', + }), + makeSummaryProvider({ + name: 'uis_request_duration', + help: 'The amount of time taken to process a request.', + }), + ], +}) +export class MetricsModule implements NestModule { + configure(consumer: MiddlewareConsumer) { + consumer + .apply(MetricsMiddleware) + .forRoutes( + ClassesController, + SubjectsController, + LessonsController, + HolidaysController, + ); + } +} diff --git a/yarn.lock b/yarn.lock index 79257a8..334dc09 100644 --- a/yarn.lock +++ b/yarn.lock @@ -805,6 +805,11 @@ consola "^2.15.0" node-fetch "^2.6.1" +"@opentelemetry/api@^1.4.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.7.0.tgz#b139c81999c23e3c8d3c0a7234480e945920fc40" + integrity sha512-AdY5wvN0P2vXBi3b29hxZgSFvdhdxPB9+f0B6s//P9Q8nibRWeA3cHm8UmLpio9ABigkVHJ5NMPk+Mz8VCCyrw== + "@otplib/core@^12.0.1": version "12.0.1" resolved "https://registry.yarnpkg.com/@otplib/core/-/core-12.0.1.tgz#73720a8cedce211fe5b3f683cd5a9c098eaf0f8d" @@ -1321,6 +1326,11 @@ "@webassemblyjs/ast" "1.11.6" "@xtuc/long" "4.2.2" +"@willsoto/nestjs-prometheus@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@willsoto/nestjs-prometheus/-/nestjs-prometheus-6.0.0.tgz#6ef4d5d5dfb04ebe982aab6f3a7893974e89a669" + integrity sha512-Krmda5CT9xDPjab8Eqdqiwi7xkZSX60A5rEGVLEDjUG6J6Rw5SCZ/BPaRk+MxNGWzUrRkM7K5FtTg38vWIOt1Q== + "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -1560,6 +1570,11 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +bintrees@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.2.tgz#49f896d6e858a4a499df85c38fb399b9aff840f8" + integrity sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw== + bl@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" @@ -4033,6 +4048,14 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +prom-client@^15.0.0: + version "15.0.0" + resolved "https://registry.yarnpkg.com/prom-client/-/prom-client-15.0.0.tgz#067da874a2aa5d2e21bd5cdba9f24a8178bdab6a" + integrity sha512-UocpgIrKyA2TKLVZDSfm8rGkL13C19YrQBAiG3xo3aDFWcHedxRxI3z+cIcucoxpSO0h5lff5iv/SXoxyeopeA== + dependencies: + "@opentelemetry/api" "^1.4.0" + tdigest "^0.1.1" + prompts@^2.0.1: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" @@ -4564,6 +4587,13 @@ tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== +tdigest@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/tdigest/-/tdigest-0.1.2.tgz#96c64bac4ff10746b910b0e23b515794e12faced" + integrity sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA== + dependencies: + bintrees "1.0.2" + terser-webpack-plugin@^5.3.7: version "5.3.9" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz#832536999c51b46d468067f9e37662a3b96adfe1"