willsoto / nestjs-prometheus Goto Github PK
View Code? Open in Web Editor NEWNestJS module for Prometheus
License: Apache License 2.0
NestJS module for Prometheus
License: Apache License 2.0
Hey!
I am using "nestjs-prometheus":"5.3.0"
with "@nestjs/core": "9.3.11",
and I am experiencing the following problem when I run my nest app in docker with HMR enabled:
dev | Entrypoint main 3.3 MiB = main.js 3.29 MiB 0.bff7ae7a987fbf27b165.hot-update.js 10.2 KiB
dev | webpack 5.76.2 compiled successfully in 22491 ms
dev | Running in env: development
dev | [HMR] Cannot apply update.
dev | [HMR] Error: A metric with the name process_cpu_user_seconds_total has already been registered.
dev | at Registry.registerMetric (/usr/src/app/server/node_modules/prom-client/lib/registry.js:67:10)
dev | at new Metric (/usr/src/app/server/node_modules/prom-client/lib/metric.js:48:13)
dev | at new Counter (/usr/src/app/server/node_modules/prom-client/lib/counter.js:12:1)
dev | at module.exports (/usr/src/app/server/node_modules/prom-client/lib/metrics/processCpuTotal.js:16:30)
dev | at Object.collectDefaultMetrics (/usr/src/app/server/node_modules/prom-client/lib/defaultMetrics.js:47:3)
dev | at Function.configureServer (/usr/src/app/server/node_modules/@willsoto/nestjs-prometheus/dist/module.js:104:24)
dev | at Function.register (/usr/src/app/server/node_modules/@willsoto/nestjs-prometheus/dist/module.js:23:28)
dev | at Object.<anonymous> (/usr/src/app/server/dist/main.js:454:50)
dev | at __webpack_require__ (/usr/src/app/server/dist/main.js:40842:33)
dev | at fn (/usr/src/app/server/dist/main.js:40949:21)
dev | [HMR] You need to restart the application!
This is one of the default prom-client metrics.
My configuration is the following:
app.module.ts
imports: [
...
PrometheusModule.register({
defaultLabels: {
app: 'test',
},
path: '/api/test/metrics',
}),
...
})
...],
providers: [
...
makeCounterProvider({
name: 'http_exception',
help: 'Counter of HTTP exceptions, based on method, path and status',
labelNames: ['method', 'path', 'status'],
}),
...
]
I see that this is a common issue, and I was able to overcome it with clearing all the metrics before loading the new hmr update:
if (module.hot) {
prom.register.clear();
module.hot.accept();
module.hot.dispose(() => app.close());
}
but is there any more Nest way to achieve this?
Hello,
First, thank you for this module !
I'm trying to make a gauge to monitor a cache size.
My problem is that I need to use the collect() method of the gauge and I don't understand how to do it inside a service.
I can't simply use myGauge.set() because an external module fill this cache, I don't do it in my service.
Is it possible in this library ?
Thx in advance !
Célian
This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.
Warning
These dependencies are deprecated:
Datasource | Name | Replacement PR? |
---|---|---|
npm | standard-version |
These updates are pending. To force PRs open, click the checkbox below.
These are blocked by an existing closed PR and will not be recreated unless you click a checkbox below.
.github/workflows/ci.yml
willsoto/actions v2.3.0
package.json
@commitlint/cli 19.3.0
@commitlint/config-conventional 19.2.2
@fastify/compress 7.0.3
@nestjs/cli 10.3.2
@nestjs/common 10.3.9
@nestjs/core 10.3.9
@nestjs/platform-express 10.3.9
@nestjs/platform-fastify 10.3.9
@nestjs/schematics 10.1.1
@nestjs/testing 10.3.9
@semantic-release/changelog 6.0.3
@semantic-release/git 10.0.1
@types/chai 4.3.16
@types/chai-as-promised 7.1.8
@types/eslint 8.56.10
@types/express 4.17.21
@types/express-serve-static-core 4.19.5
@types/mocha 10.0.7
@types/node 20.14.8
@types/sinon 17.0.3
@types/sinon-chai 3.2.12
@types/supertest 6.0.2
@typescript-eslint/eslint-plugin 7.13.1
@typescript-eslint/parser 7.13.1
c8 10.1.2
chai-as-promised 7.1.2
eslint 8.57.0
eslint-config-prettier 9.1.0
husky 9.0.11
lint-staged 15.2.7
markdown-toc 1.2.0
mocha 10.4.0
prettier 3.3.2
prettier-plugin-organize-imports 3.2.4
prettier-plugin-packagejson 2.5.0
prom-client 15.1.2
reflect-metadata 0.2.2
rimraf 5.0.7
rxjs 7.8.1
semantic-release 24.0.0
sinon 18.0.0
sinon-chai 3.7.0
standard-version 9.5.0
supertest 7.0.0
ts-node 10.9.2
typescript 5.5.2
@nestjs/common ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0
prom-client ^15.0.0
node 20.15.0
pnpm 9.4.0
pnpm 9.4.0
I need an example that demonstrates how can I add a custom metric for every route endpoint. Say, Counter or histogram.
I would like to create a middleware with default metrics instead of annotating every endpoint.
I can see the package.json license field listed Apache 2.0, but an official license in the repo would help users know if they can use your project.
https://help.github.com/en/github/building-a-strong-community/adding-a-license-to-a-repository
metrics should not be public, hence, this is better to expose it on a different port than the main application.
Do you have an example for this?
I created a new Nestjs project, Installed the lib and then copy pasted the example in the README of a counter. That worked fine.
However, forking the nestjs application into a sub-process and trying to increment the counter does not updates the metric value.
Even tough I increment the metric inside an infinite loop
let = 0;
while (true) {
console.log('incrementing prometheus counter in background... ' + i++);
service.increment();
await setTimeout(500);
}
The metric endpoint returns zero
I've created repo which replicates this issue:
https://github.com/quixote15/nestjs-prometheus-fork-issue
Can anybody help me with this?
I found this really weird issue.
When using useFactory / registerAsync, the custom controller does not work properly.
I have to use configuration, so I moved to async creation and I hit this issue which I cannot even workaround :/
If custom controller is passed to the options while using registerAsync, the controller is not used, but the base prom controller is being used and on top of that the path is being ignored completely.
Hello!
I wanted to ask a quick question
if I've registered the Prometheus module at app.module.ts for example, and I want to use Pushgateway at another module service, how can I Inject it?
Following the instructions, typescript complains about incompatible types
@Injectable()
export class Service {
constructor(@InjectMetric("metric_name") public counter: Counter) {}
}
Error message "Generic type 'Counter' requires 1 type argument(s).ts(2314)" when calling the constructor.
Looks like all metrics are generics in prom-client.
/**
* A counter is a cumulative metric that represents a single numerical value that only ever goes up
*/
export class Counter<T extends string> {
/**
* @param configuration Configuration when creating a Counter metric. Name and Help is required.
*/
constructor(configuration: CounterConfiguration<T>);
/**
* Increment for given labels
* @param labels Object with label keys and values
* @param value The number to increment with
*/
inc(labels: LabelValues<T>, value?: number): void;
/**
* Increment with value
* @param value The value to increment with
*/
inc(value?: number): void;
/**
* Return the child for given labels
* @param values Label values
* @return Configured counter with given labels
*/
labels(...values: string[]): Counter.Internal;
/**
* Reset counter values
*/
reset(): void;
/**
* Remove metrics for the given label values
* @param values Label values
*/
remove(...values: string[]): void;
}
Used versions:
"prom-client": "^12.0.0",
"@willsoto/nestjs-prometheus": "^0.1.1",
Am I missing something?
For ref: https://github.com/PayU/prometheus-api-metrics
Ideally, this lib could provide a new middleware to create those metrics.
I was wondering if there was a way to persist data through NestJS runs? After NestJS restarts, it seems that the metrics get reset.
Hi, first off, thanks for this library!
I have an app that's serving up a couple of endpoints in a Kubernetes environment. I have a global auth guard that's protecting the routes I care about. I'm excepting the public routes from the guard using a Public()
decorator as detailed in this blog.
I tried passing a custom controller to the prometheus .register()
function with the Public()
decorator on it, and I'm not having any luck.
Before I continue to go down this rabbit hole, I figured I'd ask if there is any obvious (or maybe non-obvious) way to exclude the Prometheus controller from any auth guard in place? I should also add that I'm in no way a nestjs expert. It could be that there's a way to handle this that's more a function of NestJS and less of this particular library. In any event, would love to get your take. Thanks!
Fastify version: 4.2.1
NestJS version: 9.0.3
Node version: 16x
Mac M1
- Hi owner. Our company have idea switch from Express to Fastify because we have heard about performance of it so that we do it.
- When integration with prometheus we tracking the Fastify get metrics is only response blank page instead like Express
- We tracking the issues is about the compression
Result:
- Express it have response data of metrics
- Fastify it is not response and just blank page
Expect:
- Fastify response data of metrics
We are using this project to report data to Prometheus from our NodeJS / NestJS project. We have several custom metrics - in particular histograms (created directly using @siimon/prom-client
APIs). As a result, the overall metrics size grows up to 8MB. This results in the NodeJS event loop lag getting to be as large as 1 second!
What we see:
register.resetMetrics()
causes the lag to go down almost to 0. It then starts increasing againgetPeerCertificate
and destroySSL
Has anyone encountered this sort of behavior? Any suggestions?
Do you guys have a tutorial on using this module with graphql? I keep getting an error saying that createMetricsPlugin from apollo-metrics is not a function on my prom.module.
I was using this tutorial: https://dev.to/shinigami92/how-to-setup-prometheus-metrics-for-nestjs-graphql-67n
I created the configuration below:
@Module({ imports: [ ScheduleModule.forRoot(), HttpModule, TerminusModule, PrometheusModule.register({ path: '/prometheus', defaultMetrics: { config: { labels: { host: os.hostname(), flow: 'change-payment-method' } }, enabled: true } }),
And created some individual metrics
providers: [ makeHistogramProvider({ name: 'http_request_duration_seconds', labelNames: ['method', 'queryId', 'status'], buckets: [100, 200, 400] }),
But the individual metrics does not show the default label configured in the defaultMetrics. Is this a bug, or is it expected behavior?
Hi, I am being really dense, but how do you run this package and how do you configure it with your nest username/password?
Thanks
Dan
Context:
NestJS HttpModule uses axios and it can support interceptors.
For metrics like:
southbound_client_errors_count
southbound_request_duration_seconds
On the sample code:
import { Injectable } from '@nestjs/common';
import { Counter } from 'prom-client';
import { InjectMetric } from '@willsoto/nestjs-prometheus';
@Injectable()
export class Service {
constructor(@InjectMetric('metric_name') public counter: Counter<string>) {}
}
TS compiler throws an error:
Unable to resolve signature of parameter decorator when called as an expression.
Argument of type 'typeof Service' is not assignable to parameter of type 'Record<string, unknown>'.
Index signature for type 'string' is missing in type 'typeof Service'
how to use with prometheus-gc-stats package
Nestjs has released a new major version. See https://github.com/nestjs/nest/releases/tag/v9.0.0
Currently peerDependency of @nestjs/common is looking for v7 || v8
Could you please update to support the new version!
BR Jakob
With the documentation that is in the repository it is not possible for me to understand how pushgateway works, I have the following and I have gatewayurl undefined errors.
Prom.module.ts
import { Module } from '@nestjs/common';
import { PrometheusModule } from '@willsoto/nestjs-prometheus';
import createMetricsPlugin from 'apollo-metrics';
import { plugin as apolloTracingPlugin } from 'apollo-tracing';
import { Pushgateway, register } from 'prom-client';
import { PromService } from './prom.service';
export const TRACING_PLUGIN_KEY = 'TRACING_PLUGIN_KEY';
export const METRICS_PLUGIN_KEY = 'METRICS_PLUGIN_KEY';
@Module({
imports: [
PrometheusModule.register({
pushgateway: {
url: process.env.PUSH_GATEWAY_URL,
},
defaultLabels: {
app: 'Mobile BFF',
},
}),
],
providers: [
PromService,
{
provide: TRACING_PLUGIN_KEY,
useValue: apolloTracingPlugin(),
},
{
provide: METRICS_PLUGIN_KEY,
useValue: createMetricsPlugin(register),
},
Pushgateway,
],
exports: [PromService, Pushgateway, TRACING_PLUGIN_KEY, METRICS_PLUGIN_KEY],
})
export class PromModule {}
PromService.ts
import { Injectable, OnModuleInit, Logger } from '@nestjs/common';
import * as client from 'prom-client';
@Injectable()
export class PromService implements OnModuleInit {
private readonly logger = new Logger(PromService.name);
constructor(private readonly pushgateway: client.Pushgateway) {
console.log('client', pushgateway);
}
onModuleInit() {
this.logger.log('Pushing to prometheus gateway');
setInterval(() => {
this.pushgateway.push({
jobName: 'mobile-bff',
});
}, 15000);
}
}
Error in console:
TypeError: The "url" argument must be of type string. Received undefined
at new NodeError (node:internal/errors:371:5)
at validateString (node:internal/validators:119:11)
at Url.parse (node:url:169:3)
at Object.urlParse [as parse] (node:url:156:13)
at Pushgateway.useGateway (/Users/manuel.labarca/Desktop/mobile-bff/node_modules/prom-client/lib/pushgateway.js:46:31)
Hi,
I'm trying to use the collect function for the Gauge metric, but this function is not functional for getting data inside a service.
The workaround is really bad because i use a global variable, and i instantiate the metric with Gauge class from "prom-client" :
import {Gauge} from 'prom-client';
@Injectable()
export class UpdateAssetImageService {
private gauge;
constructor() {
this.gauge = new Gauge({
name: "asset_image_generated",
help: "metric_help",
collect() {
this.set(counter);
}
})
}
and "counter" is a global variable.
How can i use the collect from module please ?
Hi All,
I am trying to add a service inject in my custom controller, it seems it doesn't support it.
Hello @willsoto, awesome package!
I am trying to have all my custom metrics carry some common tags like the service name, version and the hostname but I am not finding any easy way to do this. All paths seems to require a lot of code duplication to have these common tags set on all custom metrics.
Is there a way to add a parameter to the PrometheusModule.register(PrometheusOptions) class so that a Record<string,string> could be specified with all tags that all metrics should always carry? This would be analogous to the defaultMetrics -> config -> labels property, but it should apply to both "defaultMetrics" and "customMetrics".
What do you think? I can help with a PR if you point me in the right direction.
Version 5.4.0 introduces a prefix option that adds a prefix to all metrics. But this only works if you register the PrometheusModule in the same module that you create the metrics in. I updated from 5.3.0 to 5.4.0 and then I got the error but I also tested it using a fresh nestjs app and that also gave the error.
Steps to reproduce:
nest new project-name
@willsoto/nestjs-prometheus
and prom-client
nestjs g module module-name
Error message: Error: Nest can't resolve dependencies of the PROM_METRIC_METRIC_NAME (?). Please make sure that the argument Symbol(PROMETHEUS_OPTIONS) at index [0] is available in the ModuleNameModule context.
Pushgateway is already supported by prom-client, so adding it should be mostly straightforward.
It's helpful in microservices and standalone applications where an HTTP server is unavailable.
We would like to be able to inject a dependency into the collect function in order to be able to monitor resources configured by another nest provider for example a queue.
We have done a POC to work around the fact there seems to be no way to do this as so:
{
provide: getToken('queue_size_scheduled_queue'),
inject: [getQueueToken('scheduledQueue')],
useFactory: (queue) => {
return getOrCreateMetric('Gauge', {
name: 'queue_size_scheduled_queue',
help: 'todo',
collect: async function () {
const value = await queue.getWaitingCount();
this.set(value);
},
});
},
}
In an ideal world we would like to use an api such as:
makeGaugeProvider({
name: 'queue_size_scheduled_queue',
help: 'some help',
collectInject: [getQueueToken('scheduledQueue')],
collect: async function (queue) {
const value = await queue.getWaitingCount();
this.set(value);
}
})
Hi, I just installed your package using npm and it complains:
node_modules/@willsoto/nestjs-prometheus/dist/metrics/utils.d.ts:1:8 - error TS1192: Module '"my-project/node_modules/prom-client/index"' has no default export.
1 import client from "prom-client";
Looking at the code:
import client from "prom-client";
in your utils
does not have a default export.
I checked the installed version of prom-client
(which is 12.0.0) and indeed it only exports specific types.
Could you fix that, or am I missing something?
Thanks!
Can we also support using the fastify server? thanks
I would like to not have a controller created automatically. Instead I am creating another express app in the main.ts on a different port (9090) for this purpose.
const metricsServer = express();
const metricsMiddleware = promBundle({ includeMethod: true });
metricsServer.use(metricsMiddleware);
metricsServer.listen(9090);
As long as the prom-client version is the same between the two instances I am able to use this module to easily use prometheus.
I tried to design a NestJS Monorepo application.
Prometheus applies only in the application defined in the sourceRoot section of nest-cli.json
But for the other apps in the Monorepo, /metrics route returns
{"statusCode":404,"message":"Document was not found.","error":"Not Found"}
I am trying to inject some metrics into a middleware from my AppModule.
consumer.apply(RequestMetricsMiddleware).forRoutes('*');
And this is how my middleware is defined:
import { Injectable, Logger, NestMiddleware } from '@nestjs/common';
import { NextFunction, Request, Response } from 'express';
import { InjectMetric } from '@willsoto/nestjs-prometheus';
import { Counter, Histogram } from 'prom-client';
@Injectable()
export class RequestMetricsMiddleware implements NestMiddleware {
constructor(
@InjectMetric('node_request_operations_total') private operationsCounter: Counter<string>,
@InjectMetric('node_request_duration_seconds') private requestDurationHistogram: Histogram<string>,
) {}
use(req: Request, _: Response, next: NextFunction): void {
var startTime = performance.now();
req.on('close', () => {
var endTime = performance.now();
const totalMilliseconds = endTime - startTime;
const name = req?.body?.query?.split('{')?.[0]?.toString().trim() || req.url;
this.requestDurationHistogram.observe(totalMilliseconds / 1000); // convert to seconds
Logger.debug(`Request to ${name} took ${totalMilliseconds} milliseconds`);
});
this.operationsCounter.inc();
next();
}
}
My Prometheus Module look like so:
import { makeCounterProvider, makeGaugeProvider, makeHistogramProvider, PrometheusModule } from '@willsoto/nestjs-prometheus';
import { PrometheusController } from '../PrometheusController';
import { PrometheusStatsService } from './PrometheusStats.service';
@Module({
imports: [
PrometheusModule.register({
controller: PrometheusController,
}),
],
providers: [
PrometheusStatsService,
makeCounterProvider({
name: 'node_request_operations_total',
help: 'The total number of processed requests',
}),
makeHistogramProvider({
name: 'node_request_duration_seconds',
help: 'Histogram for the request duration in seconds',
buckets: [0, 1, 2, 5, 6, 10],
}),
],
exports: [PrometheusStatsService],
})
export class PrometheusStatsModule {}
Which I import in my AppModule
under the imports
array.
I am getting the error:
This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason:
Error: Nest can't resolve dependencies of the class RequestMetricsMiddleware {
constructor(operationsCounter, requestDurationHistogram) {
this.operationsCounter = operationsCounter;
this.requestDurationHistogram = requestDurationHistogram;
}
use(req, _, next) {
var startTime = performance.now();
req.on('close', () => {
var _a, _b, _c, _d;
var endTime = performance.now();
const totalMilliseconds = endTime - startTime;
const name = ((_d = (_c = (_b = (_a = req === null || req === void 0 ? void 0 : req.body) === null || _a === void 0 ? void 0 : _a.query) === null || _b === void 0 ? void 0 : _b.split('{')) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.toString().trim()) || req.url;
this.requestDurationHistogram.observe(totalMilliseconds / 1000);
common_1.Logger.debug(`Request to ${name} took ${totalMilliseconds} milliseconds`);
});
this.operationsCounter.inc();
next();
}
} (?, PROM_METRIC_NODE_REQUEST_DURATION_SECONDS). Please make sure that the argument PROM_METRIC_NODE_REQUEST_OPERATIONS_TOTAL at index [0] is available in the AppModule context.
Potential solutions:
- If PROM_METRIC_NODE_REQUEST_OPERATIONS_TOTAL is a provider, is it part of the current AppModule?
- If PROM_METRIC_NODE_REQUEST_OPERATIONS_TOTAL is exported from a separate @Module, is that module imported within AppModule?
@Module({
imports: [ /* the Module containing PROM_METRIC_NODE_REQUEST_OPERATIONS_TOTAL */ ]
})
at Injector.lookupComponentInParentModules (/Users/mf/Projects/a/backend/node_modules/@nestjs/core/injector/injector.js:231:19)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at Injector.resolveComponentInstance (/Users/mf/Projects/a/backend/node_modules/@nestjs/core/injector/injector.js:184:33)
at resolveParam (/Users/mf/Projects/a/backend/node_modules/@nestjs/core/injector/injector.js:106:38)
at async Promise.all (index 0)
at Injector.resolveConstructorParams (/Users/mf/Projects/a/backend/node_modules/@nestjs/core/injector/injector.js:121:27)
at Injector.loadInstance (/Users/mf/Projects/a/backend/node_modules/@nestjs/core/injector/injector.js:52:9)
at Injector.loadMiddleware (/Users/mf/Projects/a/backend/node_modules/@nestjs/core/injector/injector.js:61:9)
at MiddlewareResolver.resolveMiddlewareInstance (/Users/mf/Projects/a/backend/node_modules/@nestjs/core/middleware/resolver.js:16:9)
at async Promise.all (index 0)
Temporarily solved the issue by adding the makeCounterProvider
and makeHistogramProvider
to exports as well, although not very pretty/DRY.
Hi there. Thank you very much for your work! So far everything is working great. However, I wanted to add a call to update gauges whenever the metrics are scraped. I thought I could use Custom Controller for this but I can't find a way to inject a dependency for my custom controller into the PrometheusModule.
Example
@Controller()
export class AppPrometheusController extends PrometheusController {
constructor(private readonly someService: SomeService) {
super();
}
@Get()
async index(@Res({ passthrough: true }) response: Response): Promise<string> {
await this.someService.updateGauges();
return super.index(response);
}
}
Usage
PrometheusModule.register({
controller: AppPrometheusController,
// can't put `providers` or `imports` here?
}),
Error
{"context":"ExceptionHandler","stack":["Error: Nest can't resolve dependencies of the AppPrometheusController (?).
Please make sure that the argument SomeService at index [0] is available in the PrometheusModule context.
Potential solutions:
- If SomeService is a provider, is it part of the current PrometheusModule?
- If SomeService is exported from a separate @Module, is that module imported within PrometheusModule?
...
I saw there is also registerAsync
, createAsyncProviders
and createAsyncOptionsProvider
but as they are not well documented, I'm not sure if they would be of any help.
Currently investigating that, but it's not clear yet.
Hi
Sorry for the question, but I'm DevOps and I'm helping the development team. My doubt is why in this library I don't have the http_request_duration_seconds_xxx
metrics? Do I need to implement it myself?
could you clarify this for me?
Thanks.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.