Métricas de Node.js com Prometheus

Node Exporter monitora o sistema operacional, mas não sua aplicação. prom-client é a biblioteca oficial do Prometheus para Node.js, com ela você expõe métricas de negócio: latência por endpoint, número de usuários ativos, erros de banco por tipo, filas pendentes. Esses dados são o que realmente importa para SLOs e debugging.

Instalar prom-client e expor /metrics

prom-client é a biblioteca oficial, expõe métricas de runtime do Node.js automaticamente e permite métricas customizadas:

typescript
npm install prom-client

// src/metrics.ts
import { Registry, collectDefaultMetrics } from 'prom-client'

export const registry = new Registry()

// Coletar métricas padrão do Node.js automaticamente:
// process_cpu_seconds_total, process_memory_bytes,
// nodejs_heap_size_bytes, nodejs_eventloop_lag_seconds, etc.
collectDefaultMetrics({
  register: registry,
  prefix: 'minha_api_',     // prefixo para identificar a aplicação
  gcDurationBuckets: [0.001, 0.01, 0.1, 1, 2, 5],
})

// src/index.ts
import express from 'express'
import { registry } from './metrics'

const app = express()

// Endpoint que o Prometheus irá coletar
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', registry.contentType)
  res.send(await registry.metrics())
})

Métricas de requisições HTTP com middleware

Rastreie latência e volume de requisições por endpoint:

typescript
// src/middleware/metrics.ts
import { Histogram, Counter, Gauge, Registry } from 'prom-client'
import { Request, Response, NextFunction } from 'express'

export function createHttpMetrics(registry: Registry) {
  const httpDuration = new Histogram({
    name: 'http_request_duration_seconds',
    help: 'Duração das requisições HTTP em segundos',
    labelNames: ['method', 'route', 'status_code'],
    buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5],
    registers: [registry],
  })

  const httpRequestsTotal = new Counter({
    name: 'http_requests_total',
    help: 'Total de requisições HTTP',
    labelNames: ['method', 'route', 'status_code'],
    registers: [registry],
  })

  const httpActiveRequests = new Gauge({
    name: 'http_active_requests',
    help: 'Número de requisições ativas',
    registers: [registry],
  })

  return (req: Request, res: Response, next: NextFunction) => {
    const start = Date.now()
    httpActiveRequests.inc()

    res.on('finish', () => {
      const duration = (Date.now() - start) / 1000
      const route = req.route?.path || 'unknown'
      const labels = { method: req.method, route, status_code: res.statusCode }

      httpDuration.observe(labels, duration)
      httpRequestsTotal.inc(labels)
      httpActiveRequests.dec()
    })

    next()
  }
}

Métricas de negócio customizadas

Meça o que importa para o seu negócio, não só infraestrutura:

typescript
// src/metrics/business.ts
import { Counter, Gauge, Histogram } from 'prom-client'
import { registry } from '../metrics'

// Contagem de usuários registrados
export const usersRegistered = new Counter({
  name: 'users_registered_total',
  help: 'Total de usuários registrados',
  registers: [registry],
})

// Usuários online agora
export const activeUsers = new Gauge({
  name: 'active_users',
  help: 'Usuários ativos no momento',
  registers: [registry],
})

// Latência de operações de banco
export const dbQueryDuration = new Histogram({
  name: 'db_query_duration_seconds',
  help: 'Duração de queries no banco de dados',
  labelNames: ['operation', 'table'],
  buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1],
  registers: [registry],
})

// Uso nas rotas:
// usersRegistered.inc()                        — ao registrar usuário
// activeUsers.set(await countOnlineUsers())    — periodicamente
// const end = dbQueryDuration.startTimer()     — antes da query
// end({ operation: 'select', table: 'users' }) — após a query

Métricas de fila e background jobs

Monitore o estado de filas e workers para detectar backlog:

typescript
// src/metrics/queue.ts
import { Gauge, Counter } from 'prom-client'
import { registry } from '../metrics'

export const queueSize = new Gauge({
  name: 'queue_pending_jobs',
  help: 'Jobs pendentes na fila',
  labelNames: ['queue_name'],
  registers: [registry],
})

export const jobsProcessed = new Counter({
  name: 'jobs_processed_total',
  help: 'Jobs processados',
  labelNames: ['queue_name', 'status'],  // status: success | failed
  registers: [registry],
})

// Atualizar tamanho da fila periodicamente
setInterval(async () => {
  const pendingJobs = await getQueueSize('emails')
  queueSize.set({ queue_name: 'emails' }, pendingJobs)
}, 15000)

// PromQL para alertar quando fila cresce:
// queue_pending_jobs{queue_name="emails"} > 100

Queries PromQL para aplicação Node.js

Queries para dashboards e alertas da aplicação:

promql
# Taxa de requisições por segundo (últimos 5 min)
rate(http_requests_total[5m])

# Percentil 99 de latência por rota
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

# Taxa de erros (%)
rate(http_requests_total{status_code=~"5.."}[5m]) /
rate(http_requests_total[5m]) * 100

# Heap do Node.js usado (%)
nodejs_heap_size_used_bytes / nodejs_heap_size_total_bytes * 100

# Event loop lag (ms)
nodejs_eventloop_lag_seconds * 1000

# Requests ativas agora
http_active_requests

# SLO: % de requests respondidas em < 500ms
rate(http_request_duration_seconds_bucket{le="0.5"}[5m]) /
rate(http_request_duration_seconds_count[5m]) * 100

Não quer configurar manualmente?

Implante o VPS em menos de 5 minutos com a Runstack. Infraestrutura da OPEN Datacenter, com servidores em São Paulo.

Perguntas frequentes

prom-client: específico para Prometheus, simples, amplamente adotado. OpenTelemetry: padrão de observabilidade (métricas + traces + logs), vendor-neutral, mais complexo. Para stack Prometheus+Grafana: prom-client é suficiente. Para observabilidade completa com tracing distribuído: OpenTelemetry com exporter para Prometheus.

Cada série temporal (combinação única de labels) consome memória no Prometheus. Regra: cuidado com labels de alta cardinalidade (user_id, request_id, milhões de valores únicos). Use labels de baixa cardinalidade (method, route, status_code, dezenas de valores). 50-100 métricas customizadas com cardinalidade baixa é absolutamente normal.

Defina o SLO: "99,9% das requisições respondem em < 500ms". Meça: rate(http_request_duration_seconds_bucket{le="0.5"}[30d]) / rate(http_request_duration_seconds_count[30d]). Alert quando o error budget está se esgotando. Ferramentas como Sloth ou Pyrra geram as queries de SLO automaticamente.

Não. Exponha apenas internamente: app.get("/metrics", authMiddleware, handler) ou configure Nginx para permitir apenas o IP do Prometheus: allow 172.20.0.0/16; deny all; (rede interna Docker). Métricas revelam padrões de tráfego e uso que podem ser explorados por atacantes.

Use decorators (TypeScript) ou middleware para instrumentar sem modificar a lógica de negócio. Para Express: middleware de métricas HTTP automático. Para queries de banco: patch das funções de query (ex: prisma middleware). Para funções específicas: wrapper com startTimer()/observe().