Métricas de negócio no Prometheus: KPIs com prom-client

    Métricas técnicas dizem que o servidor está vivo; métricas de negócio dizem se o produto está saudável. Taxa de conversão caindo? Pedidos parando? Churn aumentando? Essas perguntas têm resposta quando você instrumenta o código de negócio com os mesmos counters e gauges que usa para monitorar a API.

    Definir os KPIs do produto como métricas

    Mapear os eventos importantes de negócio para tipos de métrica:

    typescript
    // lib/metricas-negocio.ts — KPIs instrumentados com prom-client:
    import { Counter, Gauge, Histogram } from 'prom-client'
    import { registry } from './registry'
    
    // ── Aquisição ──────────────────────────────────────────────────────
    export const cadastrosTotal = new Counter({
      name: 'negocio_cadastros_total',
      help: 'Total de novos usuários cadastrados',
      labelNames: ['canal', 'plano'],  // canal: 'google_oauth', 'email', 'convite'
      registers: [registry],
    })
    
    export const loginTotal = new Counter({
      name: 'negocio_logins_total',
      help: 'Total de logins realizados',
      labelNames: ['metodo', 'sucesso'],  // metodo: 'senha', 'google', 'github'
      registers: [registry],
    })
    
    // ── Ativação / Engajamento ─────────────────────────────────────────
    export const usuariosAtivos = new Gauge({
      name: 'negocio_usuarios_ativos_gauge',
      help: 'Usuários com sessão ativa agora',
      registers: [registry],
    })
    
    export const acoesRealizadas = new Counter({
      name: 'negocio_acoes_total',
      help: 'Ações realizadas pelos usuários',
      labelNames: ['tipo_acao', 'plano_usuario'],
      registers: [registry],
    })
    
    // ── Receita ────────────────────────────────────────────────────────
    export const receitaTotal = new Counter({
      name: 'negocio_receita_brl_total',
      help: 'Receita acumulada em BRL',
      labelNames: ['plano', 'periodo', 'metodo_pagamento'],
      registers: [registry],
    })
    
    export const assinaturas = new Gauge({
      name: 'negocio_assinaturas_ativas',
      help: 'Total de assinaturas ativas por plano',
      labelNames: ['plano'],
      registers: [registry],
    })
    
    // ── Retenção ───────────────────────────────────────────────────────
    export const cancelamentos = new Counter({
      name: 'negocio_cancelamentos_total',
      help: 'Total de assinaturas canceladas',
      labelNames: ['plano', 'motivo'],
      registers: [registry],
    })
    
    // ── Conversão ─────────────────────────────────────────────────────
    export const funil = new Counter({
      name: 'negocio_funil_etapa_total',
      help: 'Eventos de funil de conversão',
      labelNames: ['etapa'],  // 'landing', 'cadastro', 'trial', 'pagamento', 'ativo'
      registers: [registry],
    })

    Instrumentar os fluxos de negócio

    Integrar as métricas nos handlers críticos da aplicação:

    typescript
    // handlers/auth.ts — instrumentar fluxo de cadastro e login:
    app.post('/api/auth/register', async (req, res) => {
      const usuario = await criarUsuario(req.body)
    
      // Funil de conversão:
      funil.inc({ etapa: 'cadastro' })
      cadastrosTotal.inc({
        canal: req.body.provider ?? 'email',
        plano: usuario.plano ?? 'free',
      })
    
      res.status(201).json(usuario)
    })
    
    // handlers/pagamento.ts — instrumentar conversão de pagamento:
    app.post('/api/assinaturas/checkout', autenticar, async (req, res) => {
      const assinatura = await processarPagamento(req.body, req.usuario)
    
      if (assinatura.status === 'ativo') {
        // Receita:
        receitaTotal.inc({
          plano: assinatura.plano,
          periodo: assinatura.periodo,  // 'mensal', 'anual'
          metodo_pagamento: assinatura.metodoPagamento,
        }, assinatura.valor)
    
        // Funil de conversão:
        funil.inc({ etapa: 'pagamento' })
    
        // Atualizar gauge de assinaturas ativas:
        assinaturas.inc({ plano: assinatura.plano })
      }
    
      res.json(assinatura)
    })
    
    // handlers/cancelamento.ts:
    app.post('/api/assinaturas/cancelar', autenticar, async (req, res) => {
      const motivo = req.body.motivo ?? 'sem_motivo'
      const assin = await cancelarAssinatura(req.usuario.userId)
    
      cancelamentos.inc({ plano: assin.plano, motivo })
      assinaturas.dec({ plano: assin.plano })
    
      res.json({ status: 'cancelado' })
    })

    Sincronizar métricas de gauge com o banco

    Manter gauges atualizados consultando o banco periodicamente:

    typescript
    // Problema: gauges podem ficar desatualizados se o processo reiniciar.
    // Solução: sincronizar com o banco a cada N minutos.
    
    import { db } from './db'
    
    async function sincronizarMetricasNegocio() {
      // Buscar contagens atuais do banco:
      const [assinaturasAtivas] = await db.query<{ plano: string; total: number }[]>(
        `SELECT plano, COUNT(*) as total
         FROM assinaturas
         WHERE status = 'ativo'
         GROUP BY plano`
      )
    
      // Resetar e recarregar os gauges:
      for (const { plano, total } of assinaturasAtivas) {
        assinaturas.set({ plano }, Number(total))
      }
    
      // Usuários ativos na última hora:
      const [{ count }] = await db.query(
        "SELECT COUNT(DISTINCT usuario_id) as count FROM sessoes WHERE updated_at > NOW() - INTERVAL '1 hour'"
      )
      usuariosAtivos.set(Number(count))
    }
    
    // Sincronizar na inicialização e a cada 5 minutos:
    sincronizarMetricasNegocio()
    setInterval(sincronizarMetricasNegocio, 5 * 60 * 1000)
    
    // Coletor assíncrono (alternativa mais elegante):
    // O prom-client suporta coletores que executam lógica assíncrona:
    import { Gauge } from 'prom-client'
    
    new Gauge({
      name: 'negocio_pedidos_pendentes',
      help: 'Pedidos aguardando processamento',
      async collect() {
        const [{ count }] = await db.query(
          "SELECT COUNT(*) FROM pedidos WHERE status = 'pendente'"
        )
        this.set(Number(count))
      },
      registers: [registry],
    })

    Dashboards de negócio no Grafana

    Criar painéis de KPIs para o time de produto e gestão:

    bash
    # Queries PromQL para dashboard de negócio:
    
    # Taxa de cadastros (por canal) nos últimos 30 minutos:
    sum by (canal) (
      rate(negocio_cadastros_total[30m])
    ) * 60 * 60  # converter para por hora
    
    # Receita acumulada hoje:
    increase(negocio_receita_brl_total[24h])
    
    # Receita por plano (donut chart):
    sum by (plano) (
      increase(negocio_receita_brl_total[24h])
    )
    
    # Taxa de conversão: cadastros → pagamento (últimas 24h):
    increase(negocio_funil_etapa_total{etapa="pagamento"}[24h])
    /
    increase(negocio_funil_etapa_total{etapa="cadastro"}[24h]) * 100
    
    # Churn rate (cancelamentos / assinaturas ativas):
    sum(rate(negocio_cancelamentos_total[7d])) * 24 * 7
    /
    sum(negocio_assinaturas_ativas) * 100
    
    # Assinaturas ativas por plano (stat panel):
    sum by (plano) (negocio_assinaturas_ativas)
    
    # Dicas de organização:
    # - Usar variáveis de template para filtrar por período
    # - Separar dashboards: Técnico (DevOps) vs Produto (CEO/PM)
    # - Dashboard "Executive Summary" com os 4-5 KPIs principais em stat panels
    # - Threshold coloridos: verde > meta, amarelo = atenção, vermelho = alerta

    Alertas de negócio críticos

    Detectar anomalias no produto antes que o cliente reclame:

    yaml
    # alerts/negocio.yml:
    groups:
      - name: negocio
        rules:
    
          # Zero pedidos nos últimos 15 minutos (checkout pode estar quebrado):
          - alert: ZeroPedidos
            expr: |
              rate(negocio_pedidos_criados_total[15m]) == 0
            for: 15m
            labels:
              severity: critical
            annotations:
              summary: "Nenhum pedido nos últimos 15 minutos"
              description: "Possível problema no fluxo de checkout. Verificar logs."
    
          # Taxa de erro de pagamento acima de 10%:
          - alert: AltaTaxaErroPagamento
            expr: |
              rate(negocio_pagamentos_total{status="falhou"}[30m])
              / rate(negocio_pagamentos_total[30m]) * 100 > 10
            for: 5m
            labels:
              severity: critical
            annotations:
              summary: "Taxa de falha de pagamento acima de 10%"
              description: "Taxa atual: {{ $value | printf "%.1f" }}%. Verificar integração com gateway."
    
          # Pico de cancelamentos (possível problema de qualidade/cobrança):
          - alert: PicoCancelamentos
            expr: |
              rate(negocio_cancelamentos_total[1h]) > 3
            for: 15m
            labels:
              severity: warning
            annotations:
              summary: "Taxa de cancelamento acima do normal"
    
          # Queda brusca de usuários ativos (> 30% em 1h):
          - alert: QuedaUsuariosAtivos
            expr: |
              negocio_usuarios_ativos_gauge
              < negocio_usuarios_ativos_gauge offset 1h * 0.7
            for: 15m
            labels:
              severity: warning
            annotations:
              summary: "Queda de >30% em usuários ativos na última hora"

    $ 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