siimon/prom-client

Library not working with NextJS production build

justenau opened this issue · 1 comments

It seems there are issues with using this library in NextJS project. We were aiming to use this library for two things:

  1. Getting default metrics
  2. Setting up custom counters

All these metrics should be returned by project api/metrics endpoint.

Setup we had:

// file:  instrumentation.ts

import * as process from 'process'

export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    const promClient = await import('prom-client')
    console.log('Configuring instrumentation')

    console.log('Configuring default prom metrics')
    promClient.collectDefaultMetrics({ prefix: 'web_v2_' })

    console.log('Creating custom prom metrics')
    new promClient.Counter({
      name: 'web_v2_pages_load_counter',
      help: 'How many times pages have been opened',
    })
    new promClient.Counter({
      name: 'web_v2_error_counter',
      help: 'How many errors have occurred',
    })

    console.log(await promClient.register.getSingleMetric('web_v2_error_counter')) // This returns counter correctly
  }
}
// file: app/api/metrics/route.ts

import { NextResponse } from 'next/server'
import { register } from 'prom-client'

export async function GET() {
  return new NextResponse(await register.metrics(), { headers: [['Content-type', register.contentType]] })
}

As per NextJS docs, instrumentation.ts file is the correct place for setting up any monitoring and etc. This setup does work locally - if I start the app with yarn dev and ping the http://localhost:3000/api/metrics endpoint, the metrics are returned. I'm also able to increase the counter value in the project code and I see this counter value changing, as expected, when fetching the metrics endpoint. But once I run the production build (yarn build && yarn start), it stops working properly - the http://localhost:3000/api/metrics endpoint returns empty response.

The only way to make the default metrics fetch work in this build would be to make the collectDefaultMetrics() call in the metrics route itself, f.e.

// file: app/api/metrics/route.ts

import { NextResponse } from 'next/server'
import { register } from 'prom-client'

 promClient.collectDefaultMetrics({ prefix: 'web_v2_' })

export async function GET() {
  return new NextResponse(await register.metrics(), { headers: [['Content-type', register.contentType]] })
}

But this only works for default metrics as the custom counters are not being returned at all/not working, and this ruins the purpose of even having instrumentation.

Ok I finally figured out what the problem was so will share in case someone runs into similar issue in the future. The problem actually was with NextJS route setup - by default all such api routes (like api/metrics) get cached indefinitely (as explained in (NextJS docs) that's why I was receiving empty response. Fixed this by adding export const revalidate = 0 to the route.ts.

// file: app/api/metrics/route.ts

import { NextResponse } from 'next/server'
import { register } from 'prom-client'

export async function GET() {
  return new NextResponse(await register.metrics(), { headers: [['Content-type', register.contentType]] })
}

export const revalidate = 0