prom-client: métricas customizadas em Node.js

    prom-client é a biblioteca oficial do Prometheus para Node.js. Ela instrumenta sua API com três tipos fundamentais de métricas: counters (eventos que só crescem), gauges (valores que sobem e descem) e histogramas (distribuição de valores como latência). O endpoint /metrics expõe tudo no formato que o Prometheus sabe coletar.

    Configurar prom-client no Express

    Configuração base com métricas padrão do processo Node.js:

    typescript
    // npm install prom-client
    
    import { Registry, collectDefaultMetrics, Counter, Histogram, Gauge } from 'prom-client'
    import type { Express } from 'express'
    
    // Registry separado (melhor que o default para testes e múltiplas instâncias):
    export const registry = new Registry()
    
    // Coletar métricas padrão do processo Node.js:
    // (CPU, memória heap, event loop lag, GC, handles ativos)
    collectDefaultMetrics({
      register: registry,
      prefix: 'nodejs_',   // prefixo para identificar a aplicação
      labels: {
        app: 'minha-api',
        env: process.env.NODE_ENV ?? 'production',
      },
    })
    
    // Registrar endpoint /metrics:
    export function registrarMetricas(app: Express) {
      app.get('/metrics', async (req, res) => {
        res.set('Content-Type', registry.contentType)
        res.end(await registry.metrics())
      })
    
      // Proteger /metrics de acesso externo (apenas Prometheus interno):
      // Adicionar antes: verificar se o IP é o do Prometheus
      // ou usar middleware de autenticação simples
    }

    Counter, Gauge e Histogram na prática

    Os três tipos de métricas mais importantes com exemplos reais:

    typescript
    // lib/metricas.ts — definir todas as métricas em um único arquivo:
    import { Counter, Gauge, Histogram } from 'prom-client'
    import { registry } from './registry'
    
    // COUNTER: só cresce — total de requisições, erros, emails enviados:
    export const httpRequestsTotal = new Counter({
      name: 'http_requests_total',
      help: 'Total de requisições HTTP recebidas',
      labelNames: ['method', 'route', 'status_code'],
      registers: [registry],
    })
    
    export const emailsEnviadosTotal = new Counter({
      name: 'emails_enviados_total',
      help: 'Total de emails enviados com sucesso',
      labelNames: ['tipo'],  // ex: 'boas_vindas', 'recuperacao_senha', 'notificacao'
      registers: [registry],
    })
    
    // GAUGE: sobe e desce — conexões ativas, jobs na fila, tamanho do cache:
    export const conexoesAtivas = new Gauge({
      name: 'websocket_conexoes_ativas',
      help: 'Número de conexões WebSocket ativas',
      registers: [registry],
    })
    
    export const jobsNaFila = new Gauge({
      name: 'jobs_fila_tamanho',
      help: 'Quantidade de jobs pendentes na fila',
      labelNames: ['fila'],  // ex: 'emails', 'webhooks', 'exports'
      registers: [registry],
    })
    
    // HISTOGRAM: distribuição de valores — latência de requisições, tamanho de payload:
    export const httpDuration = new Histogram({
      name: 'http_request_duration_seconds',
      help: 'Latência das requisições HTTP em segundos',
      labelNames: ['method', 'route', 'status_code'],
      // Buckets: valores de limite para cada "balde" do histograma
      // Adaptar para sua API: se latência típica é 50-200ms, use buckets menores
      buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5],
      registers: [registry],
    })
    
    export const dbQueryDuration = new Histogram({
      name: 'db_query_duration_seconds',
      help: 'Latência de queries ao banco de dados',
      labelNames: ['query_name', 'tabela'],
      buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1],
      registers: [registry],
    })

    Middleware de instrumentação automática

    Registrar latência e contagem de todas as rotas automaticamente:

    typescript
    // middleware/metricas.ts — instrumentar todas as rotas automaticamente:
    import { httpRequestsTotal, httpDuration } from '../lib/metricas'
    
    export function middlewareMetricas(req: Request, res: Response, next: NextFunction) {
      const start = process.hrtime.bigint()
    
      // Normalizar o path (evitar cardinality explosão com IDs dinâmicos):
      // /api/pedidos/123 → /api/pedidos/:id
      // /api/usuarios/abc-def → /api/usuarios/:id
      const route = req.route?.path ?? normalizarPath(req.path)
    
      res.on('finish', () => {
        const duracaoMs = Number(process.hrtime.bigint() - start) / 1e9
    
        const labels = {
          method: req.method,
          route,
          status_code: String(res.statusCode),
        }
    
        httpRequestsTotal.inc(labels)
        httpDuration.observe(labels, duracaoMs)
      })
    
      next()
    }
    
    function normalizarPath(path: string): string {
      return path
        .replace(//[0-9a-f]{8}-[0-9a-f-]{27}/g, '/:uuid')  // UUIDs
        .replace(//d+/g, '/:id')                            // IDs numéricos
        .replace(//[0-9a-f]{24}/g, '/:objectId')             // MongoDB ObjectIDs
    }
    
    // Registrar no Express ANTES das rotas:
    app.use(middlewareMetricas)
    app.use('/api', roteador)

    Instrumentar banco de dados e serviços externos

    Medir latência de queries e chamadas a APIs externas:

    typescript
    // lib/db.ts — wrapper do pg com métricas:
    import { Pool } from 'pg'
    import { dbQueryDuration } from './metricas'
    
    const pool = new Pool({ connectionString: process.env.DATABASE_URL })
    
    export async function query<T>(
      queryName: string,
      tabela: string,
      sql: string,
      params?: unknown[]
    ): Promise<T[]> {
      const end = dbQueryDuration.startTimer({ query_name: queryName, tabela })
    
      try {
        const result = await pool.query(sql, params)
        end()  // registrar duração com sucesso
        return result.rows
      } catch (err) {
        end()  // registrar duração mesmo em caso de erro
        throw err
      }
    }
    
    // Usar na aplicação:
    const pedidos = await query('listar_pedidos', 'pedidos',
      'SELECT * FROM pedidos WHERE usuario_id = $1',
      [userId]
    )
    
    // Métricas de serviços externos (ex: API de pagamento):
    import { Histogram } from 'prom-client'
    
    export const externalApiDuration = new Histogram({
      name: 'external_api_duration_seconds',
      help: 'Latência de chamadas a APIs externas',
      labelNames: ['servico', 'endpoint', 'status'],
      buckets: [0.1, 0.25, 0.5, 1, 2, 5, 10],
      registers: [registry],
    })
    
    // Wrapper para fetch com métricas:
    export async function fetchComMetricas(servico: string, url: string, opts?: RequestInit) {
      const end = externalApiDuration.startTimer({ servico, endpoint: new URL(url).pathname })
      const res = await fetch(url, opts)
      end({ status: String(res.status) })
      return res
    }

    Métricas de negócio customizadas

    Monitorar KPIs da aplicação além de métricas técnicas:

    typescript
    // Métricas de negócio — o que importa para o produto:
    import { Counter, Gauge } from 'prom-client'
    
    // Eventos de negócio:
    export const pedidosCriados = new Counter({
      name: 'negocios_pedidos_criados_total',
      help: 'Total de pedidos criados',
      labelNames: ['plano', 'canal'],  // ex: plano='premium', canal='web'
      registers: [registry],
    })
    
    export const receita = new Counter({
      name: 'negocios_receita_reais_total',
      help: 'Receita total em reais',
      labelNames: ['plano', 'metodo_pagamento'],
      registers: [registry],
    })
    
    export const usuariosAtivos = new Gauge({
      name: 'negocios_usuarios_ativos',
      help: 'Usuários com sessão ativa no momento',
      registers: [registry],
    })
    
    // Usar nos handlers da aplicação:
    app.post('/api/pedidos', autenticar, async (req, res) => {
      const pedido = await criarPedido(req.body, req.usuario)
    
      // Métricas técnicas (via middleware automático)
      // + Métricas de negócio (explícitas):
      pedidosCriados.inc({
        plano: req.usuario.plano,
        canal: req.headers['x-source'] as string || 'web',
      })
    
      receita.inc({
        plano: req.usuario.plano,
        metodo_pagamento: pedido.metodoPagamento,
      }, pedido.valorTotal)
    
      res.status(201).json(pedido)
    })
    
    // Alerta de negócio: queda de pedidos (possível problema na checkout):
    # - alert: QuedaNaPedidos
    #   expr: rate(negocios_pedidos_criados_total[30m]) < 0.1
    #   for: 15m
    #   annotations:
    #     summary: "Taxa de pedidos abaixo de 6/hora — verificar checkout"

    $ runstack deploy --plan starter

    Não quer configurar manualmente?

    Não quer configurar manualmente? Implante o VPS para Node.js em menos de 3 minutos com a Runstack. Infraestrutura da OPEN DATACENTER, com servidores no Brasil.

    Perguntas frequentes