SkeLLLa / fastify-metrics

Prometheus metrics exporter for Fastify
https://savelife.in.ua/en/donate-en/
MIT License
100 stars 33 forks source link

expose metrics via another port #43

Open Matanelc opened 2 years ago

Matanelc commented 2 years ago

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.

SkeLLLa commented 2 years ago

Hi, sorry for late answer.

Theoretically it's possible, but it will require creating of additional server. However it's easier to do manually. Just set endpoint param to undefined value and then you may create additional server which will do the same that's done in https://github.com/SkeLLLa/fastify-metrics/blob/master/src/index.ts#L193.

bcomnes commented 2 years ago

Another example in case its helpful to anyone. Here is registering the metrics plugin on a main server, and serving up the stats on a different server/port:

import fp from 'fastify-plugin'
import Fastify from 'fastify'

/**
 * This plugins adds promethius metrics
 *
 * @see https://gitlab.com/m03geek/fastify-metrics
 */
export default fp(async function (fastify, opts) {
  fastify.register((await import('fastify-metrics')).default, {
    defaultMetrics: { enabled: true },
    endpoint: null,
    name: 'metrics',
    routeMetrics: { enabled: true }
  })

  const promServer = Fastify({
    logger: true
  })

  promServer.route({
    url: '/metrics',
    method: 'GET',
    logLevel: 'info',
    schema: {
      // hide route from swagger plugins
      hide: true
    },
    handler: async (_, reply) => {
      reply.type('text/plain').send(await fastify.metrics.client.register.metrics())
    }
  })

  const start = async () => {
    try {
      await promServer.listen({
        port: 9091,
        host: '0.0.0.0'
      })
    } catch (err) {
      promServer.log.error(err)
      promServer.log('promethius server stopped')
      process.exit(1)
    }
  }

  fastify.addHook('onClose', async (instance) => {
    await promServer.close()
  })
  await start()
},
{
  name: 'prom',
  dependencies: ['env']
})
SkeLLLa commented 2 years ago

Maybe to make this a bit easier we can add export of such object to a var like fastify.metrics.route

{
    url: '/metrics',
    method: 'GET',
    logLevel: 'info',
    schema: {
      // hide route from swagger plugins
      hide: true
    },
    handler: async (_, reply) => {
      reply.type('text/plain').send(await fastify.metrics.client.register.metrics())
    }
  }
joeholdcroft commented 1 month ago

@SkeLLLa Hey! We're also keen to host our metrics from another port, but would like to avoid duplicating too much from this library to do so. Would be happy to look at contributing something for this use case. Given this was last discussed a few years ago, what would your preferred implementation look like?

One option: allow passing an alternate fastify server that the route gets registered to:

  const metricsServer = fastify();
  await server.register(metricsPlugin, {
    endpointServer: metricsServer,
    endpoint: {
      url: '/metrics',
      logLevel: 'warn',
    }
  });

Another option: allow passing a function for endpoint param to expose the endpoint yourself:

  const metricsServer = fastify();
  await server.register(metricsPlugin, {
    endpoint: (route) => (
      metricsServer.route({
        …route,
        logLevel: 'warn',
      })
    );
  });

Open to other ideas too 😄

SkeLLLa commented 1 month ago

@joeholdcroft I think both options are fine, so if you submitting the PR you can choose one what suites your needs. Might be the first one will be more types-friendly and it will be easier to make that endpoint server optional.

gmaclennan commented 2 weeks ago

Is this a reasonable solution to this @SkeLLLa? I don't entirely understand how prom-client and this plugin work under the hood, specifically what is in an instance and what is global. I think I could even skip passing the promClient as an option, since it seems everything is global in that anyway.


import promClient from 'prom-client'
import metricsPlugin from 'fastify-metrics'

const mainServer = createFastify()
const metricsServer = createFastify()

mainServer.register(metricsPlugin, { endpoint: null, promClient })
metricsServer.register(
  metricsPlugin,
  {
    endpoint: '/metrics',
    promClient,
    defaultMetrics: { enabled: false },
    routeMetrics: { enabled: false },
  }
)
await mainServer.listen({ port: 8080 })
await metricsServer.listen({ port: 8081 })

)
SkeLLLa commented 2 weeks ago

@gmaclennan I think it's good idea. In prom-client what's global - it's a default global Registry. So in theory it could even work without passing promClient to metrics server, since they will share default global registry.

So worth to try the following code:

import promClient from 'prom-client'
import metricsPlugin from 'fastify-metrics'

const mainServer = createFastify()
const metricsServer = createFastify()

mainServer.register(metricsPlugin, { endpoint: null })
metricsServer.register(
  metricsPlugin,
  {
    endpoint: '/metrics',
    defaultMetrics: { enabled: false },
    routeMetrics: { enabled: false },
  }
)
await mainServer.listen({ port: 8080 })
await metricsServer.listen({ port: 8081 })

)