Prometheus: métricas customizadas na aplicação

Métricas de infraestrutura (CPU, RAM) mostram o sintoma, não a causa. Métricas de negócio, pedidos por segundo, latência de checkout, fila de emails, mostram o que realmente importa. Instrumentar a aplicação com Prometheus expõe esses indicadores para o Grafana e Alertmanager.

Instrumentar Node.js com prom-client

Adicionar endpoint /metrics ao Express:

typescript
// npm install prom-client

import express from 'express'
import { register, collectDefaultMetrics, Counter, Histogram, Gauge } from 'prom-client'

// Coletar métricas padrão do processo Node.js:
collectDefaultMetrics({ prefix: 'api_' })

// ── Definir métricas customizadas ────────────────────────
// Counter: só aumenta (requisições, erros, eventos)
const httpRequests = new Counter({
  name: 'api_http_requests_total',
  help: 'Total de requisições HTTP',
  labelNames: ['method', 'route', 'status'],
})

// Histogram: distribuição de valores (latência, tamanho de payload)
const httpDuration = new Histogram({
  name: 'api_http_duration_seconds',
  help: 'Latência das requisições HTTP',
  labelNames: ['method', 'route', 'status'],
  buckets: [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2, 5],
})

// Gauge: pode subir e descer (conexões ativas, fila de jobs)
const activeConnections = new Gauge({
  name: 'api_active_connections',
  help: 'Conexões ativas no momento',
})

// Middleware para instrumentar automaticamente todas as rotas:
app.use((req, res, next) => {
  const end = httpDuration.startTimer()
  res.on('finish', () => {
    const labels = { method: req.method, route: req.route?.path ?? req.path, status: res.statusCode }
    httpRequests.inc(labels)
    end(labels)
  })
  next()
})

// Expor endpoint para o Prometheus fazer scrape:
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', register.contentType)
  res.send(await register.metrics())
})

Métricas de negócio: pedidos e receita

Instrumentar fluxos críticos da aplicação:

typescript
// Métricas de negócio customizadas
import { Counter, Histogram, Gauge } from 'prom-client'

const pedidosCriados = new Counter({
  name: 'negocio_pedidos_criados_total',
  help: 'Total de pedidos criados',
  labelNames: ['plano', 'origem'],
})

const receita = new Counter({
  name: 'negocio_receita_total_brl',
  help: 'Receita total em BRL',
  labelNames: ['plano'],
})

const filaPendente = new Gauge({
  name: 'negocio_fila_pedidos_pendentes',
  help: 'Pedidos aguardando processamento',
})

const tempoPagamento = new Histogram({
  name: 'negocio_pagamento_duracao_segundos',
  help: 'Tempo para processar pagamento',
  buckets: [0.5, 1, 2, 5, 10, 30],
})

// Usar na aplicação:
async function criarPedido(dados: PedidoInput) {
  const timer = tempoPagamento.startTimer()
  try {
    const pedido = await db.pedido.create(dados)
    pedidosCriados.inc({ plano: dados.plano, origem: dados.origem })
    receita.inc({ plano: dados.plano }, dados.valor)
    filaPendente.inc()
    timer({ status: 'success' })
    return pedido
  } catch (err) {
    timer({ status: 'error' })
    throw err
  }
}

Configurar scrape no Prometheus

Adicionar a aplicação como target no prometheus.yml:

yaml
# prometheus.yml — adicionar job para a aplicação:
scrape_configs:
  # Infraestrutura (já existente):
  - job_name: 'node_exporter'
    static_configs:
      - targets: ['localhost:9100']

  # Aplicação Node.js:
  - job_name: 'minha-api'
    static_configs:
      - targets: ['localhost:3000']
    metrics_path: '/metrics'
    scrape_interval: 15s

  # Múltiplas instâncias (PM2 cluster com portas diferentes):
  - job_name: 'api-cluster'
    static_configs:
      - targets:
          - 'localhost:3000'
          - 'localhost:3001'
          - 'localhost:3002'
          - 'localhost:3003'
    relabel_configs:
      - source_labels: [__address__]
        target_label: instance

# Recarregar Prometheus sem restart:
curl -X POST http://localhost:9090/-/reload

Queries PromQL úteis para dashboards

Consultas para visualizar as métricas da aplicação:

bash
# Taxa de requisições por segundo (últimos 5 min):
rate(api_http_requests_total[5m])

# Latência p95 por rota:
histogram_quantile(0.95,
  sum by (route, le) (
    rate(api_http_duration_seconds_bucket[5m])
  )
)

# Taxa de erros (5xx) por minuto:
sum(rate(api_http_requests_total{status=~"5.."}[1m]))

# Porcentagem de erros em relação ao total:
100 * sum(rate(api_http_requests_total{status=~"5.."}[5m]))
      /
      sum(rate(api_http_requests_total[5m]))

# Pedidos criados por hora:
increase(negocio_pedidos_criados_total[1h])

# Receita das últimas 24h:
increase(negocio_receita_total_brl[24h])

# Fila de pedidos pendentes (instantâneo):
negocio_fila_pedidos_pendentes

# Alertas PromQL — configurar no Alertmanager:
# Alerta: taxa de erros > 5% por 5 min
ALERT HighErrorRate
  IF rate(api_http_requests_total{status=~"5.."}[5m]) / rate(api_http_requests_total[5m]) > 0.05
  FOR 5m
  LABELS { severity="warning" }

Instrumentar Python com prometheus_client

Métricas Prometheus em APIs FastAPI/Flask:

python
# pip install prometheus-client prometheus-fastapi-instrumentator

from fastapi import FastAPI
from prometheus_client import Counter, Histogram, Gauge, generate_latest, CONTENT_TYPE_LATEST
from prometheus_fastapi_instrumentator import Instrumentator
from fastapi.responses import Response
import time

app = FastAPI()

# Instrumentação automática (latência, status codes):
Instrumentator().instrument(app).expose(app)

# Métricas customizadas:
pedidos_counter = Counter(
    'pedidos_criados_total',
    'Total de pedidos criados',
    ['plano', 'status']
)

processamento_hist = Histogram(
    'processamento_duracao_segundos',
    'Duração do processamento de pedidos',
    buckets=[0.1, 0.5, 1.0, 2.5, 5.0, 10.0]
)

@app.post("/pedidos")
async def criar_pedido(dados: PedidoInput):
    inicio = time.time()
    try:
        pedido = await processar_pedido(dados)
        pedidos_counter.labels(plano=dados.plano, status='success').inc()
        return pedido
    except Exception as e:
        pedidos_counter.labels(plano=dados.plano, status='error').inc()
        raise
    finally:
        processamento_hist.observe(time.time() - inicio)

Não quer configurar manualmente?

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

Perguntas frequentes

Counter: só sobe, nunca reset (exceto reinício), requisições, erros. Gauge: sobe e desce, conexões ativas, temperatura, fila. Histogram: distribui valores em buckets configuráveis, latência, tamanho de payload; permite calcular percentis (p50, p95, p99). Summary: similar ao Histogram mas calcula percentis no cliente, menos flexível para agregação entre instâncias. Prefira Histogram a Summary na maioria dos casos.

Overhead mínimo com prom-client, Counter.inc() e Histogram.observe() são operações em memória extremamente rápidas (~microsegundos). O endpoint /metrics serializa as métricas apenas quando o Prometheus faz scrape (a cada 15s por padrão). Para APIs de alto tráfego: overhead < 0.1% de CPU.

No Nginx: restringir /metrics por IP (allow 10.0.0.1; deny all;) ou adicionar autenticação básica. No Express: middleware que verifica header Authorization ou IP. O Prometheus no mesmo servidor pode acessar via localhost sem exposição pública. Nunca exponha /metrics publicamente, revela detalhes internos da aplicação.

Cardinalidade importa, cada combinação única de valores de labels cria uma série de tempo separada. Labels com poucos valores (status: 200/404/500, método: GET/POST): OK. Labels de alta cardinalidade (userId, requestId, IP): NUNCA use, cria milhões de séries, estoura a memória do Prometheus. Regra: labels devem ter < 100 valores únicos.

Depende da arquitetura. Se Prometheus e aplicação estão na mesma VPS (loopback): HTTP local é seguro. Se Prometheus está em servidor separado acessando via rede privada: também OK. Se o scrape passa pela internet pública: use HTTPS e autenticação. A maioria dos setups self-hosted usa rede privada, sem HTTPS no /metrics é aceitável.