-
Notifications
You must be signed in to change notification settings - Fork 378
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Simple counter fails on Next.js -- Avoid using Node.js-specific APIs #584
Comments
I too am wondering how to solve this problem |
Next.js does some weird bundling stuff and have an odd global environment. There might be something off due to that? Probably better to ask in their support channels. I don't think we've had requests to work in a browser context before, but that should be fine. Happy to take PRs allowing it. |
I've managed to get this working on Next.js, but it required quite a few changes on prom-client which I'm not sure if the project would be willing to consider merging @SimenB. You can check the changes in this fork here: https://github.com/siimon/prom-client/compare/master...pablote:prom-client:feature/next-friendly-changes?expand=1. To summarize:
|
Making |
For simple default metrics, creating an api route works well in Next.js: collectDefaultMetrics({});
const prometheus = async (_req: NextApiRequest, res: NextApiResponse) => {
res.setHeader('Content-type', register.contentType);
res.send(await register.metrics());
};
export default prometheus; As for using In the end my import { GetServerSideProps, GetServerSidePropsContext } from 'next';
import { Counter, Histogram, register } from 'prom-client';
// Even though it may look like nothing has been registered yet, if next
// recompiles due to HMR prom-client will throw errors when calling
// new Histogram()/new Gauge()/new Counter() as metrics with the same name
// already exist in the registry.
// https://github.com/siimon/prom-client/issues/196
register.removeSingleMetric('nextjs_gSSP_duration_seconds');
register.removeSingleMetric('nextjs_page_requests_total');
export const histograms = {
gSSPDurationSeconds: new Histogram({
name: 'nextjs_gSSP_duration_seconds',
help: 'Duration of gSSP in seconds',
labelNames: baseLabelNames,
buckets: [0.01, 0.05, 0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10],
}),
};
export const counters = {
pageRequestsTotal: new Counter({
name: 'nextjs_page_requests_total',
help: 'Count of page requests',
labelNames: baseLabelNames,
}),
};
// Register metrics on the default registry
register.registerMetric(histograms.gSSPDurationSeconds);
register.registerMetric(counters.pageRequestsTotal);
export const observedGetServerSideProps =
(gSSP: GetServerSideProps, route: string) => async (context: GetServerSidePropsContext) => {
// All incoming requests will have some derivable labels
const baseLabels = { route, foo: 'bar' }
// Run gSSP
const endGSSPDurationTimer = histograms.gSSPDurationSeconds.startTimer();
const response = await gSSP(context);
const duration = endGSSPDurationTimer(baseLabels);
// Capture metrics
histograms.gSSPDurationSeconds.observe(duration);
counters.pageRequestsTotal.labels(baseLabels).inc();
// Return gSSP response result
return response;
}; That is used like this const unwrappedGetServerSideProps: GetServerSideProps = () => {
// your gSSP here
};
export const getServerSideProps: GetServerSideProps = observedGetServerSideProps(
unwrappedGetServerSideProps, '/[destination]'
); I did find that the I would be very interested in improvements to allow us to use the middleware in Next rather than this wrapper, as currently we cannot use |
Seems like we've run into a similar picture. Using I've seen some suggestions like using a custom server to host the Next.js app, but that's not an option for me. That said, it looks like Next.js is integrating OpenTelemetry as a functionality, although it's still an experimental feature. So I'll probably not pursue this any further and see how that pans outs. |
Unfortunately this is where we're at too - I've only added |
It doesn't have to be one or the other I think. You can have an app using OT pushing metrics to a OT collector (this is very similar to pushing prom metrics to a push gateway). And then have Prometheus consume those from the collector, check this out: https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/examples/demo That said, last time I tried this in Next.js, it was only pushing trace/spans style metrics, and not counter/gauge/histogram prom style metrics. Not sure if it's a WIP and they plan to add that in the future. |
@harry-gocity @pablote I've seen a few examples online with this approach, can you confirm that calling I am trying to migrate a Next app away from using a custom server and currently |
We've since upgraded to next 14 (still pages dir) and now have an export async function register() {
console.log('Configuring instrumentation');
if (process.env.NEXT_RUNTIME === 'nodejs') {
const prom = await import('prom-client');
console.log('Configuring default prom metrics');
prom.collectDefaultMetrics({});
console.log('Creating custom prom metrics');
new prom.Histogram({
...
});
new prom.Counter({
...
});
new prom.Counter({
...
});
}
} We don't call |
Thanks @harry-gocity I was missing the
At the end of the register block I am able to call import { NextApiRequest, NextApiResponse } from 'next';
import { register } from 'prom-client';
const handler = async (_: NextApiRequest, res: NextApiResponse) => {
const metric = register.getSingleMetric(
'my_app_http_request_count',
);
console.log(metric); // returns undefined
console.log(register.getMetricsAsArray()); // returns []
res.send('Text');
};
export default handler; Here is my exact export async function register() {
console.log('Configuring instrumentation');
if (process.env.NEXT_RUNTIME === 'nodejs') {
const prom = await import('prom-client');
console.log('Configuring default prom metrics');
prom.collectDefaultMetrics({
prefix: 'my_app',
});
console.log('Creating custom prom metrics');
new prom.Counter({
name: 'my_app_http_request_count',
help: 'Count of HTTP requests made to the app',
labelNames: ['method'],
});
await prom.register.metrics(); // is this needed?
console.log(prom.register.getMetricsAsArray()); // returns correct data
console.log(prom.register.getSingleMetric('my_app_http_request_count')); // returnes correct data
}
} |
We don't call import { NextApiRequest, NextApiResponse } from 'next';
import { register } from 'prom-client';
const prometheus = async (_req: NextApiRequest, res: NextApiResponse) => {
res.setHeader('Content-type', register.contentType);
res.send(await register.metrics());
};
export default prometheus; |
After a lot of debugging I finally found what was missing. For anyone who has a similar issue, in my ...
experimental: {
instrumentationHook: true,
serverComponentsExternalPackages: ['prom-client'],
},
... and then calling Also in relation to the original issue, while you can't increment a counter directly within the |
I'm having issues making this work on production build. I setup instrumentation as per this example #584 (comment) , in the api route called |
FWIW I've somewhat started to give up with this lib - we're now using |
Just FYI I was able to resolve my issue, left more in-depth explanation here: |
@harry-gocity can you share some examples using |
I suppose this might help you. |
Hi, I know this library is supposed to be framework agnostic, but it seems to be generating a bad interaction with Next.js. I have a counter defined like this:
This counter works well if used from within an API route for example. Thing is, I want it to catch every single request, so I use it where it makes most sense, in a middleware:
Doing this throws the following error:
This might not make a lot of sense if you don't know Next.js but some code, like the middleware, runs on a different JS runtime where it cannot be assumed that Node.js global APIs are available. My main question is: why would a counter.inc() call process.uptime? Seems unnecessary.
Ideally, no Node.js specific APIs should be used, only standard Web APIs, unless specifically required (and being able to opt-out).
thanks for the help
The text was updated successfully, but these errors were encountered: