feat: add metrics

This commit is contained in:
Sergey Krylov 2026-03-25 06:47:16 +03:00
parent 06c4b10a94
commit d9074b4365
8 changed files with 141 additions and 3 deletions

View File

@ -9,11 +9,11 @@ services:
restart: always restart: always
env_file: env_file:
- .env.production.local - .env.production.local
expose:
- '9101'
networks: networks:
- teacinema - teacinema
networks: networks:
teacinema: teacinema:
external: true external: true

View File

@ -35,6 +35,7 @@
"@teacinema/common": "^1.0.0", "@teacinema/common": "^1.0.0",
"@teacinema/contracts": "^1.0.0", "@teacinema/contracts": "^1.0.0",
"@teacinema/passport": "^1.0.0", "@teacinema/passport": "^1.0.0",
"@willsoto/nestjs-prometheus": "^6.0.2",
"amqp-connection-manager": "^5.0.0", "amqp-connection-manager": "^5.0.0",
"amqplib": "^0.10.9", "amqplib": "^0.10.9",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
@ -42,6 +43,7 @@
"dotenv-expand": "^12.0.3", "dotenv-expand": "^12.0.3",
"ioredis": "^5.9.2", "ioredis": "^5.9.2",
"prisma": "^7.3.0", "prisma": "^7.3.0",
"prom-client": "^15.1.3",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1" "rxjs": "^7.8.1"
}, },

View File

@ -1,6 +1,8 @@
import { Module } from '@nestjs/common' import { Module } from '@nestjs/common'
import { ConfigModule } from '@nestjs/config' import { ConfigModule } from '@nestjs/config'
import { ObservabilityModule } from '@/observability/observability.module'
import { import {
dabataseEnv, dabataseEnv,
grpcEnv, grpcEnv,
@ -32,6 +34,7 @@ import { UsersModule } from './modules/users/users.module'
}), }),
PrismaModule, PrismaModule,
RedisModule, RedisModule,
ObservabilityModule,
AuthModule, AuthModule,
OtpModule, OtpModule,
UsersModule, UsersModule,

View File

@ -12,6 +12,6 @@ async function bootstrap() {
createGrpcServer(app, config) createGrpcServer(app, config)
await app.startAllMicroservices() await app.startAllMicroservices()
await app.init() await app.listen(9101)
} }
bootstrap() bootstrap()

View File

@ -0,0 +1,55 @@
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor
} from '@nestjs/common'
import { InjectMetric } from '@willsoto/nestjs-prometheus'
import { Counter, Histogram } from 'prom-client'
import { Observable, tap } from 'rxjs'
@Injectable()
export class GrpcMetricsInterceptor implements NestInterceptor {
private readonly SERVICE_NAME: string
public constructor(
@InjectMetric('grpc_request_duration_seconds')
private readonly histogram: Histogram<string>,
@InjectMetric('grpc_request_total')
private readonly counter: Counter<string>
) {
this.SERVICE_NAME = 'auth-service'
}
public intercept(
context: ExecutionContext,
next: CallHandler
): Observable<any> {
const handler = context.getHandler()
const entTimer = this.histogram.startTimer({
service: this.SERVICE_NAME,
method: handler.name
})
return next.handle().pipe(
tap({
next: () => {
this.counter.inc({
service: this.SERVICE_NAME,
method: handler.name,
status: 'OK'
})
entTimer()
},
error: () => {
this.counter.inc({
service: this.SERVICE_NAME,
method: handler.name,
status: 'ERROR'
})
entTimer()
}
})
)
}
}

View File

@ -0,0 +1,40 @@
import { Global, Module } from '@nestjs/common'
import { APP_INTERCEPTOR } from '@nestjs/core'
import {
makeCounterProvider,
makeHistogramProvider,
PrometheusModule
} from '@willsoto/nestjs-prometheus'
import { GrpcMetricsInterceptor } from './grpc-metrics.interceptor'
@Global()
@Module({
imports: [
PrometheusModule.register({
path: '/metrics',
defaultMetrics: {
enabled: true
}
})
],
providers: [
makeHistogramProvider({
name: 'grpc_request_duration_seconds',
help: 'GRPC request latency',
labelNames: ['service', 'method'],
buckets: [0.05, 0.1, 0.2, 0.3, 0.5, 1, 1.5, 2, 3, 5]
}),
makeCounterProvider({
name: 'grpc_request_total',
help: 'Total grpc requests',
labelNames: ['service', 'method', 'status']
}),
GrpcMetricsInterceptor,
{
provide: APP_INTERCEPTOR,
useClass: GrpcMetricsInterceptor
}
]
})
export class MetricsModule {}

View File

@ -0,0 +1,8 @@
import { Module } from '@nestjs/common'
import { MetricsModule } from './metrics/metrics.module'
@Module({
imports: [MetricsModule]
})
export class ObservabilityModule {}

View File

@ -1197,6 +1197,11 @@
dependencies: dependencies:
consola "^3.2.3" consola "^3.2.3"
"@opentelemetry/api@^1.4.0":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe"
integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==
"@paralleldrive/cuid2@^2.2.2": "@paralleldrive/cuid2@^2.2.2":
version "2.3.1" version "2.3.1"
resolved "https://registry.yarnpkg.com/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz#3d62ea9e7be867d3fa94b9897fab5b0ae187d784" resolved "https://registry.yarnpkg.com/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz#3d62ea9e7be867d3fa94b9897fab5b0ae187d784"
@ -2039,6 +2044,11 @@
"@webassemblyjs/ast" "1.14.1" "@webassemblyjs/ast" "1.14.1"
"@xtuc/long" "4.2.2" "@xtuc/long" "4.2.2"
"@willsoto/nestjs-prometheus@^6.0.2":
version "6.0.2"
resolved "https://registry.yarnpkg.com/@willsoto/nestjs-prometheus/-/nestjs-prometheus-6.0.2.tgz#d603764a923848442ed092411716c0bf211de01f"
integrity sha512-ePyLZYdIrOOdlOWovzzMisIgviXqhPVzFpSMKNNhn6xajhRHeBsjAzSdpxZTc6pnjR9hw1lNAHyKnKl7lAPaVg==
"@xtuc/ieee754@^1.2.0": "@xtuc/ieee754@^1.2.0":
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
@ -2309,6 +2319,11 @@ baseline-browser-mapping@^2.9.0:
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.15.tgz#6baaa0069883f50a99cdb31b56646491f47c05d7" resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.15.tgz#6baaa0069883f50a99cdb31b56646491f47c05d7"
integrity sha512-kX8h7K2srmDyYnXRIppo4AH/wYgzWVCs+eKr3RusRSQ5PvRYoEFmR/I0PbdTjKFAoKqp5+kbxnNTFO9jOfSVJg== integrity sha512-kX8h7K2srmDyYnXRIppo4AH/wYgzWVCs+eKr3RusRSQ5PvRYoEFmR/I0PbdTjKFAoKqp5+kbxnNTFO9jOfSVJg==
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: bl@^4.1.0:
version "4.1.0" version "4.1.0"
resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
@ -4968,6 +4983,14 @@ prisma@^7.3.0:
mysql2 "3.15.3" mysql2 "3.15.3"
postgres "3.4.7" postgres "3.4.7"
prom-client@^15.1.3:
version "15.1.3"
resolved "https://registry.yarnpkg.com/prom-client/-/prom-client-15.1.3.tgz#69fa8de93a88bc9783173db5f758dc1c69fa8fc2"
integrity sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==
dependencies:
"@opentelemetry/api" "^1.4.0"
tdigest "^0.1.1"
promise-breaker@^6.0.0: promise-breaker@^6.0.0:
version "6.0.0" version "6.0.0"
resolved "https://registry.yarnpkg.com/promise-breaker/-/promise-breaker-6.0.0.tgz#107d2b70f161236abdb4ac5a736c7eb8df489d0f" resolved "https://registry.yarnpkg.com/promise-breaker/-/promise-breaker-6.0.0.tgz#107d2b70f161236abdb4ac5a736c7eb8df489d0f"
@ -5554,6 +5577,13 @@ tapable@^2.2.0, tapable@^2.2.1, tapable@^2.3.0:
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.0.tgz#7e3ea6d5ca31ba8e078b560f0d83ce9a14aa8be6" resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.0.tgz#7e3ea6d5ca31ba8e078b560f0d83ce9a14aa8be6"
integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg== integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==
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.16: terser-webpack-plugin@^5.3.16:
version "5.3.16" version "5.3.16"
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz#741e448cc3f93d8026ebe4f7ef9e4afacfd56330" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz#741e448cc3f93d8026ebe4f7ef9e4afacfd56330"