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:
// 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:
// 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:
// 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:
// 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:
// 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
Conteúdos relacionados