skellla / fastify-metrics Goto Github PK
View Code? Open in Web Editor NEWPrometheus metrics exporter for Fastify
Home Page: https://savelife.in.ua/en/donate-en/
License: MIT License
Prometheus metrics exporter for Fastify
Home Page: https://savelife.in.ua/en/donate-en/
License: MIT License
doesn't work with current version and i get error:
Error: A metric with the name process_cpu_user_seconds_total has already been registered.
Since some 3rd party librairies I use in my project are now only packaged for ESM, I decided to migrate my TypeScript project from CommonJS to ESM ("module": "nodenext"
and "moduleResolution": "nodenext"
in tsconfig.json
)
I have an issue with fastify-metrics
: when registering, the type of metricsPlugin
is no longer considered as matching the expected type :
import metricsPlugin from 'fastify-metrics'
...
fastify.register(metricsPlugin, { endpoint: '/metrics' })
=>
src/app.ts:52:18 - error TS2769: No overload matches this call.
Overload 1 of 3, '(plugin: FastifyPluginCallback<{ endpoint: string; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>, opts?: FastifyRegisterOptions<...> | undefined): FastifyInstance<...> & PromiseLike<...>', gave the following error.
Argument of type 'typeof import("/workspace/coucou/server/node_modules/fastify-metrics/dist/index")' is not assignable to parameter of type 'FastifyPluginCallback<{ endpoint: string; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>'.
Type 'typeof import("/workspace/coucou/server/node_modules/fastify-metrics/dist/index")' provides no match for the signature '(instance: FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProvider>, opts: { ...; }, done: (err?: Error | undefined) => void): void'.
Overload 2 of 3, '(plugin: FastifyPluginAsync<{ endpoint: string; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>, opts?: FastifyRegisterOptions<...> | undefined): FastifyInstance<...> & PromiseLike<...>', gave the following error.
Argument of type 'typeof import("/workspace/coucou/server/node_modules/fastify-metrics/dist/index")' is not assignable to parameter of type 'FastifyPluginAsync<{ endpoint: string; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>'.
Type 'typeof import("/workspace/coucou/server/node_modules/fastify-metrics/dist/index")' provides no match for the signature '(instance: FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProvider>, opts: { ...; }): Promise<...>'.
Overload 3 of 3, '(plugin: FastifyPluginCallback<{ endpoint: string; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger> | FastifyPluginAsync<...> | Promise<...> | Promise<...>, opts?: FastifyRegisterOptions<...> | undefined): FastifyInstance<...> & PromiseLike<...>', gave the following error.
Argument of type 'typeof import("/workspace/coucou/server/node_modules/fastify-metrics/dist/index")' is not assignable to parameter of type 'FastifyPluginCallback<{ endpoint: string; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger> | FastifyPluginAsync<...> | Promise<...> | Promise<...>'.
52 fastify.register(metricsPlugin, { endpoint: '/metrics' })
Am I doing something wrong? It worked when my project was CommonJS.
As a workaround I did this:
fastify.register(metricsPlugin as unknown as FastifyPluginAsync<Partial<IMetricsPluginOptions>>, {
endpoint: '/metrics'
})
Hello, currently it's not possible to register the plugin multiple times. An error is thrown by the prom client that the metric was already added. This happening because the plugin uses the global prom registry.
This feature is very valuable for testing.
Reading the documentation, I understood that if pass enableRouteMetrics: false
at the plugin configuration, the plugin would only stop exporting route metrics, but would still export a route with default metrics if endpoint
is configured. However, this is not happening. When I use the following configuration, accessing /metrics
results in 404:
app.register(metricsPlugin, {
enableRouteMetrics: false,
endpoint: '/metrics',
});
Looks like you are double-exporting a default when importing in node esm module mode.
var a = await import('fastify-metrics')
undefined
> a
[Module: null prototype] {
__esModule: true,
default: {
default: <ref *1> [AsyncFunction (anonymous)] {
default: [Circular *1],
[Symbol(skip-override)]: true,
[Symbol(fastify.display-name)]: 'index-auto-0',
[Symbol(plugin-meta)]: [Object]
}
}
}
>
The current plugin has enableRouteMetrics turned on by default
fastify-metrics will record all http requests and put them in memory
when the server suffers a Challenge Collapsar Attack
, the data in memory will grow and never be cleared
because grafana gets statistics every once in a while, the entire nodejs process is blocked
eventually, the server becomes unavailable
You can use https://github.com/projectdiscovery/nuclei to perform simulation tests (./nuclei -u domain
)
This tool will make 150 requests per second, with 10 concurrent
Hi, thanks for your amazing repo!
I would like to gather some custom metrics but I didn't find some tests that describe this kind of feature. Is it possible to add a custom gauge to prom-client. If yes, how?
I can send you a PR if needed.
prom-client
had a major release which this package needs to update to.
(node:54086) [FSTDEP018] FastifyDeprecation: You are accessing the deprecated "request.routerMethod" property. Use "request.routeOptions.method" instead. Property "req.routerMethod" will be removed in
fastify@5
.
"fastify": "4.23.2",
"fastify-metrics": "10.3.2",
It would be great to support authentication for scraping requests.
api proposal:
app.register(fastifyMetrics, {
endpoint: '/metrics',
authFn: (req) => { return req.body.apiKey === 'myPassphrase' }
})
(node:463) [FSTDEP016] FastifyDeprecation: You are accessing the deprecated "request.routeConfig" property. Use "request.routeOptions.config" instead. Property "req.routeConfig" will be removed in `fastify@5`.
FastifyDeprecation: You are accessing the deprecated "request.routeConfig" property. Use "request.routeOptions.config" instead. Property "req.routeConfig" will be removed in `fastify@5`.
at Object.emit (/srv/shorturl_redirector/node_modules/process-warning/index.js:55:13)
at _Request.get (/srv/shorturl_redirector/node_modules/fastify/lib/request.js:217:15)
at FastifyMetrics.defaultGetRouteLabel (/srv/shorturl_redirector/node_modules/fastify-metrics/src/fastify-metrics.ts:88:15)
at Object.<anonymous> (/srv/shorturl_redirector/node_modules/fastify-metrics/src/fastify-metrics.ts:337:28)
at onResponseHookIterator (/srv/shorturl_redirector/node_modules/fastify/lib/hooks.js:270:10)
at next (/srv/shorturl_redirector/node_modules/fastify/lib/hooks.js:243:18)
at hookRunner (/srv/shorturl_redirector/node_modules/fastify/lib/hooks.js:265:5)
at Response.onResFinished (/srv/shorturl_redirector/node_modules/fastify/lib/reply.js:775:7)
at Response.emit (node:events:523:35)
at Response.emit (node:domain:489:12)
Do you have any dashboards that you recommend for the default stats this adds?
Is there a way to enable some sort of heuristic for grouping labels or manually specifying routes because with default configuration, my metrics is becoming too big? It has records for each possibly route /api/v1/projects/{id}.
For version 6.x.
Here are the records just for 2 requests - /api/v1/projects/2 and /api/v1/projects/3.
http_request_duration_seconds_bucket{le="0.05",method="GET",route="/GET/api/v1/projects/2",status_code="404"} 1
http_request_duration_seconds_bucket{le="0.1",method="GET",route="/GET/api/v1/projects/2",status_code="404"} 1
http_request_duration_seconds_bucket{le="0.5",method="GET",route="/GET/api/v1/projects/2",status_code="404"} 1
http_request_duration_seconds_bucket{le="1",method="GET",route="/GET/api/v1/projects/2",status_code="404"} 1
http_request_duration_seconds_bucket{le="3",method="GET",route="/GET/api/v1/projects/2",status_code="404"} 1
http_request_duration_seconds_bucket{le="5",method="GET",route="/GET/api/v1/projects/2",status_code="404"} 1
http_request_duration_seconds_bucket{le="10",method="GET",route="/GET/api/v1/projects/2",status_code="404"} 1
http_request_duration_seconds_bucket{le="+Inf",method="GET",route="/GET/api/v1/projects/2",status_code="404"} 1
http_request_duration_seconds_sum{method="GET",route="/GET/api/v1/projects/2",status_code="404"} 0.000185834
http_request_duration_seconds_count{method="GET",route="/GET/api/v1/projects/2",status_code="404"} 1
http_request_duration_seconds_bucket{le="0.05",method="GET",route="/GET/api/v1/projects/3",status_code="404"} 1
http_request_duration_seconds_bucket{le="0.1",method="GET",route="/GET/api/v1/projects/3",status_code="404"} 1
http_request_duration_seconds_bucket{le="0.5",method="GET",route="/GET/api/v1/projects/3",status_code="404"} 1
http_request_duration_seconds_bucket{le="1",method="GET",route="/GET/api/v1/projects/3",status_code="404"} 1
http_request_duration_seconds_bucket{le="3",method="GET",route="/GET/api/v1/projects/3",status_code="404"} 1
http_request_duration_seconds_bucket{le="5",method="GET",route="/GET/api/v1/projects/3",status_code="404"} 1
http_request_duration_seconds_bucket{le="10",method="GET",route="/GET/api/v1/projects/3",status_code="404"} 1
http_request_duration_seconds_bucket{le="+Inf",method="GET",route="/GET/api/v1/projects/3",status_code="404"} 1
Thank you!
Hi, I would like to know if that possible to expose the metrics to via other port.
in express we can do it with https://www.npmjs.com/package/express-prometheus-middleware
is there something that we can do with the current plugin ?
right now im exposing it on the same port as the app but i understood that this is not the best practice.
With the second release candidate of fastify 4 being out, are there any plans to support Fastify v4?
(node:3218427) [FSTDEP012] FastifyDeprecation: Request#context property access is deprecated. Please use "Request#routeConfig" or "Request#routeSchema" instead for accessing Route settings. The "Request#context" will be removed in fastify@5
.
my fastify version 4.7.0
We could include https://nodejs.org/api/perf_hooks.html#performanceeventlooputilizationutilization1-utilization2 to the list of official metrics. A few more explanation reads:
https://nodesource.com/blog/event-loop-utilization-nodejs
https://www.nearform.com/blog/event-loop-utilization-with-hpa/
Default metrics like system CPU have the default label (foo=bar), however, custom metrics like accountDeleted
do not have the default label, even though it has been registered. As far as I can recall, this used to work with 8.x.
metrics.ts
import client from 'prom-client';
client.register.setDefaultLabels({foo: 'bar'});
const accountDeleted = new client.Counter({
name: 'account_deleted',
help: 'Adds one whenever an account is deleted'
});
export const metrics = {
accountDeleted,
defaultLabels,
register: client.register,
};
app.ts
await app.register(fastifyMetrics, {
endpoint: '/metrics',
defaultMetrics: {
enabled: true,
labels: {foo: 'bar'},
register: metrics.register
}
});
Result in metrics:
account_deleted 1
process_cpu_user_seconds_total{foo="bar"} 3.608122
Expected:
account_deleted{foo="bar"} 1
process_cpu_user_seconds_total{foo="bar"} 3.608122
After this line: https://github.com/SkeLLLa/fastify-metrics/blob/master/src/index.ts#L164 in the onResponse
hook, add:
console.log('context.config.url: ' , context.config.url);
console.log('context.config.statsId: ' , context.config.statsId);
console.log('request.raw.url: ' , request.raw.url);
console.log('routeId: ' , routeId);
$ curl --get http://localhost/foo/\?x\=1
$ curl --head http://localhost/foo/\?x\=1
Last line ('routeId: ') should be /foo/
in both cases
GET
✅
context.config.url: /foo/
context.config.statsId: undefined
request.raw.url: /foo/?x=1
routeId: /foo/
HEAD
❌
context.config.url: undefined
context.config.statsId: undefined
request.raw.url: /foo/?x=1
routeId: /foo/?x=1
This splits up the metrics for the /foo/
route and creates thousands of differently-labeled metrics:
http_request_duration_seconds_bucket{le="0.05",method="HEAD",route="/foo/?x=1",status_code="200"} 1
http_request_duration_seconds_bucket{le="0.1",method="HEAD",route="/foo/?x=1",status_code="200"} 1
http_request_duration_seconds_bucket{le="0.5",method="HEAD",route="/foo/?x=1",status_code="200"} 1
http_request_duration_seconds_bucket{le="1",method="HEAD",route="/foo/?x=1",status_code="200"} 1
http_request_duration_seconds_bucket{le="3",method="HEAD",route="/foo/?x=1",status_code="200"} 1
http_request_duration_seconds_bucket{le="5",method="HEAD",route="/foo/?x=1",status_code="200"} 1
http_request_duration_seconds_bucket{le="10",method="HEAD",route="/foo/?x=1",status_code="200"} 1
http_request_duration_seconds_bucket{le="+Inf",method="HEAD",route="/foo/?x=1",status_code="200"} 1
http_request_duration_seconds_bucket{le="0.05",method="HEAD",route="/foo/?x=2",status_code="200"} 1
http_request_duration_seconds_bucket{le="0.1",method="HEAD",route="/foo/?x=2",status_code="200"} 1
http_request_duration_seconds_bucket{le="0.5",method="HEAD",route="/foo/?x=2",status_code="200"} 1
http_request_duration_seconds_bucket{le="1",method="HEAD",route="/foo/?x=2",status_code="200"} 1
http_request_duration_seconds_bucket{le="3",method="HEAD",route="/foo/?x=2",status_code="200"} 1
http_request_duration_seconds_bucket{le="5",method="HEAD",route="/foo/?x=2",status_code="200"} 1
http_request_duration_seconds_bucket{le="10",method="HEAD",route="/foo/?x=2",status_code="200"} 1
http_request_duration_seconds_bucket{le="+Inf",method="HEAD",route="/foo/?x=2",status_code="200"} 1
http_request_duration_seconds_bucket{le="0.05",method="HEAD",route="/foo/?x=3",status_code="200"} 1
http_request_duration_seconds_bucket{le="0.1",method="HEAD",route="/foo/?x=3",status_code="200"} 1
http_request_duration_seconds_bucket{le="0.5",method="HEAD",route="/foo/?x=3",status_code="200"} 1
http_request_duration_seconds_bucket{le="1",method="HEAD",route="/foo/?x=3",status_code="200"} 1
http_request_duration_seconds_bucket{le="3",method="HEAD",route="/foo/?x=3",status_code="200"} 1
http_request_duration_seconds_bucket{le="5",method="HEAD",route="/foo/?x=3",status_code="200"} 1
http_request_duration_seconds_bucket{le="10",method="HEAD",route="/foo/?x=3",status_code="200"} 1
http_request_duration_seconds_bucket{le="+Inf",method="HEAD",route="/foo/?x=3",status_code="200"} 1
etc. etc.
Any consideration to supporting opentelemetry's metrics sdk? This would make this library agnostic to the specific metrics backend (prometheus or otherwise).
Thanks for the great work!
I'm trying to use route patterns for monitoring (as it's a Prometheus metrics labels best practice) but I cannot find an option to use the route pattern instead of its values. This is an example of what I'm trying to achieve:
-http_request_duration_seconds_bucket{le="0.005",method="GET",route="/jobs/Op4X46WTqqnCKWgV4q",status_code="404"} 0
+http_request_duration_seconds_bucket{le="0.005",method="GET",route="/jobs/:id",status_code="404"} 0
I'd like to use the url
defined when registering a new fastify route, e.g:
fastify.route({
method: 'GET',
url: '/jobs/:id',
handler: handlers.getIds(),
});
These are the versions I'm using:
fastify-metrics: ^8.0.0
fastify: ^3.6.0
Is there any way I can configure this?
Only a predefined set of labels is supported: method
, status
and route
.
Sometimes it would be useful to support "custom" labels set, so that we could:
This would allow to support more fine-grained metrics.
Hey hi!
After updating to v7.4.0, I ran into some issues with my other config options. It appears that the update doesn't let any other plugins or custom config options be entered other than the ones for this plugin.
I haven't fully figured out the issue, but it looks like something with the overwriting of the context config? The @fastify/rate-limit plugin I'm using doesn't implement any types, and config should still work for any config, even ones without types defined.
Package version things:
@fastify/rate-limit: 7.4.0
fastify: 4.7.0
fastify-metrics: 9.2.2
Thank you!
# deps
"fastify": "4.6.0",
"fastify-metrics": "9.2.2",
"prom-client": "14.1.0",
await
must be added when register, or http metrics can not be exposed, except the metrics router.
The example.js can reproduce this bug, while example.ts is ok.
if (enableDefaultMetrics)
wrap the whole plugin, including the hooks
Lines 60 to 167 in 9aa6453
Hi,
testing out your great repo. There is a bug in regards to registering custom metrics via the included prom-client:
Once the LabelNames
property is being used (to declare labels for a custom metric), the metric registration fails. This seems to work just fine in the prom-client
package, here's an example regarding LabelNames
taken from their docs adjusted for fastify-metrics
// (1) this works
const res_gauge_1 = new fastify.metrics.client.Gauge({
name: 'test_gauge_1',
help: 'my test gauge 1',
});
// (2) this does NOT work
const res_gauge_2 = new fastify.metrics.client.Gauge({
name: 'test_gauge_2',
help: 'my test gauge 2',
labelNames: ['test'], // <--- breaks registration
});
See link above that this should work.
stack:
fastify-metrics: 7.4.0 (latest)
Node: 16.5.0
OS; Win 10 x64
Thanks.
After this line
Line 107 in 9aa6453
opts.histogram.registers[0] instanceof client.Registry // true
extendedOpts.histogram.registers[0] instanceof client.Registry // false
This leads to
TypeError: registryInstance.registerMetric is not a function
at /Users/simen/repos/monorepo/node_modules/prom-client/lib/histogram.js:77:26
at Array.forEach (<anonymous>)
at new Histogram (/Users/simen/repos/monorepo/node_modules/prom-client/lib/histogram.js:73:20)
at fastifyMetrics (/Users/simen/repos/monorepo/node_modules/fastify-metrics/dist/index.js:64:27)
at Plugin.exec (/Users/simen/repos/monorepo/node_modules/avvio/plugin.js:129:17)
at Boot.loadPlugin (/Users/simen/repos/monorepo/node_modules/avvio/plugin.js:263:10)
at Task.release (/Users/simen/repos/monorepo/node_modules/fastq/queue.js:140:16)
at worked (/Users/simen/repos/monorepo/node_modules/fastq/queue.js:182:10)
at /Users/simen/repos/monorepo/node_modules/avvio/plugin.js:266:7
at done (/Users/simen/repos/monorepo/node_modules/avvio/plugin.js:198:5)
at check (/Users/simen/repos/monorepo/node_modules/avvio/plugin.js:222:9)
at internal/process/task_queues.js:153:7
at AsyncResource.runInAsyncScope (async_hooks.js:186:9)
at AsyncResource.runMicrotask (internal/process/task_queues.js:150:8)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
Hello,
I upgraded fastify-metrics
from 8.0.0
to 9.2.1
. Changed plugin registration code to use new configuration options. Metrics are being collected and exposed under the route I want. However, I didn't found any way to add custom prefix to default metric names. prefix
option available in 8.0.0
no longer works.
I am wondering if this is an expected behavior? And if so, what is the rationale behind this breaking change or maybe there are some workarounds?
With custom metrics everything is fine. I've included code and endpoint output in sections below.
Code:
app.register(require("fastify-metrics"), {
prefix: 'app_', // this no longer works. I failed to find an equivalent for this
routeBlacklist: [
`/internal/metrics`,
`/internal/health/readiness`
]
})
Endpoint output:
# HELP nodejs_version_info Node.js version info.
# TYPE nodejs_version_info gauge
nodejs_version_info{version="v16.15.0",major="16",minor="15",patch="0"} 1
Code:
app.register(require("fastify-metrics"), {
prefix: 'app_',
blacklist: [
`/internal/metrics`,
`/internal/health/readiness`
],
enableDefaultMetrics: true,
enableRouteMetrics: true
})
Endpoint output:
# HELP nodejs_version_info Node.js version info.
# TYPE nodejs_version_info gauge
app_nodejs_version_info{version="v16.15.0",major="16",minor="15",patch="0"} 1
Having encountered performance degradation after introducing fastify-metrics, we've attempted doing some of the obvious performance optimizations.
Here are the results (benchmarks included):
kibertoad/fastify-prometheus-nitro#1
I'm not sending PR as-is because it is pretty intrusive, but I can replicate same or all of it in original repo if perf increase seems significant enough for you, and changes acceptable.
@SkeLLLa What do you think about adding new HTTP-related default metrics like total req count, req per sec, req per min, failure req count?
You already have a lot of what it takes to support these metrics: ignoring http methods/routes, subscriptions on fastify hooks, gathering status codes, etc.
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.