Como implementar auditoria de chamadas de API com logs estruturados

1. Fundamentos da Auditoria de API e Logs Estruturados

1.1. O que é auditoria de chamadas de API e por que ela é essencial

Auditoria de chamadas de API é o processo de registrar, monitorar e analisar todas as interações entre clientes e servidores de API. Em ambientes modernos, onde dezenas de microsserviços se comunicam constantemente, a auditoria se torna crítica para:

  • Conformidade regulatória (LGPD, GDPR, SOX, PCI-DSS) — exigem trilhas de auditoria completas e imutáveis
  • Rastreabilidade forense — identificar exatamente quem fez o quê, quando e de onde
  • Segurança — detectar padrões anômalos, tentativas de invasão e vazamento de dados
  • Depuração e troubleshooting — entender falhas em produção com precisão cirúrgica

1.2. Diferença entre logs tradicionais e logs estruturados

Logs tradicionais (texto livre) são frágeis e difíceis de consultar:

[2025-01-15 14:30:22] INFO - Usuário admin acessou /api/users/123 - sucesso

Logs estruturados (JSON) são máquina-legíveis e consultáveis:

{
  "timestamp": "2025-01-15T14:30:22.123Z",
  "level": "info",
  "correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "userId": "admin",
  "action": "GET",
  "resource": "/api/users/123",
  "statusCode": 200,
  "responseTimeMs": 45,
  "sourceIp": "192.168.1.100"
}

1.3. Campos obrigatórios em um log de auditoria

Campo Descrição Exemplo
timestamp Momento exato da requisição (ISO 8601) 2025-01-15T14:30:22.123Z
requestId Identificador único da requisição uuid-v4
userId Identificador do usuário autenticado "user_12345"
action Método HTTP GET, POST, PUT, DELETE
resource Endpoint ou recurso acessado /api/orders/987
statusCode Código de resposta HTTP 200, 401, 500
payloadResumido Corpo da requisição (sanitizado) {"email": "@"}

2. Projetando um Schema de Log de Auditoria Robusto

2.1. Campos de identificação

{
  "correlationId": "uuid-v4",       // Rastreia requisição através de microsserviços
  "sessionId": "sess_abc123",       // Sessão do usuário no frontend
  "sourceIp": "203.0.113.42",       // IP de origem (anonimizado se necessário)
  "userAgent": "Mozilla/5.0...",    // Identificação do cliente
  "authMethod": "JWT",              // Método de autenticação usado
  "tenantId": "tenant_xyz"          // Para sistemas multi-tenant
}

2.2. Campos de contexto

{
  "httpMethod": "POST",
  "endpoint": "/api/orders",
  "queryParams": {"status": "pending", "page": "1"},
  "headers": {
    "content-type": "application/json",
    "x-request-id": "abc-123"
  },
  "requestSize": 1024,              // Tamanho em bytes
  "apiVersion": "v2"
}

2.3. Campos de resultado

{
  "statusCode": 201,
  "responseTimeMs": 234,
  "responseSize": 512,
  "error": null,                    // Preenchido apenas em caso de erro
  "errorStack": null,               // Stack trace (apenas em ambientes internos)
  "sensitiveDataMasked": true,      // Indicador de sanitização
  "cacheHit": false                 // Se houve cache
}

3. Implementação de Middleware de Auditoria para APIs REST

3.1. Middleware genérico (exemplo em Node.js com Express)

// middleware/auditoria.js
const { v4: uuidv4 } = require('uuid');
const logger = require('./logger');

function auditoriaMiddleware(req, res, next) {
  const correlationId = req.headers['x-correlation-id'] || uuidv4();
  req.correlationId = correlationId;

  const inicio = Date.now();

  // Captura a resposta original
  const originalSend = res.send;
  res.send = function(body) {
    const duracao = Date.now() - inicio;

    const logEntry = {
      timestamp: new Date().toISOString(),
      correlationId,
      userId: req.user?.id || 'anonymous',
      sessionId: req.session?.id,
      sourceIp: req.ip,
      httpMethod: req.method,
      endpoint: req.originalUrl,
      queryParams: sanitizarParams(req.query),
      statusCode: res.statusCode,
      responseTimeMs: duracao,
      requestSize: JSON.stringify(req.body).length,
      responseSize: JSON.stringify(body).length,
      userAgent: req.headers['user-agent'],
      authMethod: req.user ? 'JWT' : 'NONE',
      error: res.statusCode >= 400 ? body : null
    };

    logger.info('auditoria_api', logEntry);
    return originalSend.call(this, body);
  };

  next();
}

3.2. Sanitização de dados sensíveis

function sanitizarDados(body) {
  if (!body || typeof body !== 'object') return body;

  const camposSensiveis = ['senha', 'password', 'token', 'creditCard', 'cpf', 'email'];
  const sanitizado = { ...body };

  for (const campo of camposSensiveis) {
    if (campo in sanitizado) {
      sanitizado[campo] = '***MASCARADO***';
    }
  }

  return sanitizado;
}

3.3. Geração de identificadores únicos

// Gera UUID v4 para correlação
const correlationId = uuidv4();

// Exemplo de rastreamento entre microsserviços
// Serviço A envia requisição para Serviço B
fetch('http://servico-b/api/dados', {
  headers: {
    'x-correlation-id': correlationId,
    'x-span-id': uuidv4()
  }
});

4. Estruturação e Saída dos Logs com Ferramentas Modernas

4.1. Uso de bibliotecas de logging estruturado

Pino (Node.js) — mais rápido que Winston:

const pino = require('pino');

const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  formatters: {
    level: (label) => ({ level: label }),
    bindings: (bindings) => ({ pid: bindings.pid, host: bindings.hostname })
  },
  redact: ['req.headers.authorization', 'req.body.password'],
  timestamp: pino.stdTimeFunctions.isoTime
});

// Uso no middleware
logger.info({
  correlationId: 'abc-123',
  endpoint: '/api/users',
  statusCode: 200,
  responseTimeMs: 45
}, 'auditoria_api');

Winston (Node.js) — mais flexível:

const winston = require('winston');

const auditoriaLogger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  defaultMeta: { service: 'api-gateway' },
  transports: [
    new winston.transports.File({ 
      filename: 'logs/auditoria.log',
      maxsize: 5242880, // 5MB
      maxFiles: 5
    }),
    new winston.transports.Console()
  ]
});

4.2. Níveis de log

// Sucesso — nível info
logger.info({ statusCode: 200, endpoint: '/api/orders' }, 'requisicao_sucesso');

// Lentidão — nível warn (threshold > 500ms)
if (responseTimeMs > 500) {
  logger.warn({ responseTimeMs, endpoint }, 'requisicao_lenta');
}

// Falha — nível error
logger.error({ 
  statusCode: 500, 
  error: err.message, 
  stack: err.stack.split('\n')[0] 
}, 'erro_interno');

4.3. Múltiplos destinos de saída

const transports = [
  // Console para desenvolvimento
  new transports.Console({ format: format.prettyPrint() }),

  // Arquivo rotacionado para auditoria
  new transports.File({ 
    filename: 'auditoria-%DATE%.log',
    datePattern: 'YYYY-MM-DD',
    maxSize: '20m',
    maxFiles: '14d'
  }),

  // Transporte para Elasticsearch
  new ElasticsearchTransport({
    level: 'info',
    client: new Client({ node: 'http://elasticsearch:9200' }),
    index: 'auditoria-api',
    pipeline: 'logs-pipeline'
  })
];

5. Armazenamento e Indexação de Logs de Auditoria

5.1. Escolha do backend de armazenamento

Ferramenta Ideal para Pontos fortes
Elasticsearch Consultas complexas, dashboards Full-text search, agregações
Loki Baixo custo, integração Grafana Label-based, sem indexação
S3 + Athena Longo prazo, data lake Barato, consultas SQL
ClickHouse Alta performance, grandes volumes Compressão, consultas rápidas

5.2. Estratégias de retenção

// Exemplo de política de retenção no Elasticsearch
PUT _ilm/policy/auditoria_policy
{
  "policy": {
    "phases": {
      "hot": { "min_age": "0ms", "actions": { "rollover": { "max_size": "50GB" } } },
      "warm": { "min_age": "7d", "actions": { "readonly": {} } },
      "cold": { "min_age": "30d", "actions": { "freeze": {} } },
      "delete": { "min_age": "365d", "actions": { "delete": {} } }
    }
  }
}

5.3. Indexação de campos-chave

// Mapeamento no Elasticsearch
PUT /auditoria-api/_mapping
{
  "properties": {
    "correlationId": { "type": "keyword" },
    "userId": { "type": "keyword" },
    "endpoint": { "type": "keyword" },
    "statusCode": { "type": "integer" },
    "timestamp": { "type": "date" },
    "sourceIp": { "type": "ip" },
    "responseTimeMs": { "type": "integer" },
    "httpMethod": { "type": "keyword" }
  }
}

6. Consulta e Análise dos Logs de Auditoria

6.1. Rastreamento por correlation ID

// Elasticsearch Query
GET /auditoria-api/_search
{
  "query": {
    "term": { "correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }
  },
  "sort": [{ "timestamp": "asc" }]
}

// LogQL (Loki)
{service="api-gateway"} |= "a1b2c3d4-e5f6-7890-abcd-ef1234567890"

6.2. Dashboards de auditoria

// Query para dashboard — top 10 endpoints mais acessados
GET /auditoria-api/_search
{
  "size": 0,
  "aggs": {
    "top_endpoints": {
      "terms": { "field": "endpoint", "size": 10 },
      "aggs": {
        "avg_response_time": { "avg": { "field": "responseTimeMs" } },
        "error_rate": {
          "filter": { "range": { "statusCode": { "gte": 400 } } }
        }
      }
    }
  }
}

6.3. Alertas baseados em padrões suspeitos

// Alerta no Grafana — múltiplas tentativas de acesso negado
ALERT MuitosAcessosNegados
IF rate(auditoria_api_statusCode{statusCode="401"}[5m]) > 10
FOR 2m
LABELS { severity = "critical" }
ANNOTATIONS {
  summary = "Possível ataque de força bruta detectado",
  description = "Mais de 10 tentativas de acesso negado por minuto"
}

// Alerta — endpoint raro sendo acessado
ALERT EndpointRaro
IF absent(auditoria_api_endpoint{endpoint="/api/admin/debug"})
FOR 1m
LABELS { severity = "warning" }

7. Boas Práticas e Considerações de Segurança

7.1. Mascaramento de dados sensíveis

// Função de mascaramento genérica
function mascararPII(dados) {
  const regras = {
    email: (v) => v.replace(/(.{2}).*(@.*)/, '$1***$2'),
    cpf: (v) => v.replace(/(\d{3})\d{6}(\d{2})/, '$1******$2'),
    creditCard: (v) => v.replace(/\d{12}(\d{4})/, '************$1'),
    telefone: (v) => v.replace(/(\d{2})\d{4}(\d{4})/, '$1****$2')
  };

  for (const [campo, regex] of Object.entries(regras)) {
    if (dados[campo]) {
      dados[campo] = regex(dados[campo]);
    }
  }
  return dados;
}

7.2. Controle de acesso aos logs

// RBAC no Kibana
PUT _security/role/auditor_viewer
{
  "cluster": [],
  "indices": [
    {
      "names": ["auditoria-api-*"],
      "privileges": ["read"],
      "field_security": {
        "grant": ["timestamp", "endpoint", "statusCode", "responseTimeMs"],
        "except": ["userId", "sourceIp", "headers"]
      }
    }
  ]
}

// Logs imutáveis (WORM) — exemplo com S3 Object Lock
aws s3api put-object-lock-configuration \
  --bucket logs-auditoria \
  --object-lock-configuration '{
    "ObjectLockEnabled": "Enabled",
    "Rule": {
      "DefaultRetention": {
        "Mode": "COMPLIANCE",
        "Days": 365
      }
    }
  }'

7.3. Impacto no desempenho

// Logging assíncrono com buffer
const { createLogger, transports, format } = require('winston');
const { AsyncLocalStorage } = require('async_hooks');

const auditBuffer = [];
const FLUSH_INTERVAL = 1000; // 1 segundo

// Bufferiza logs e envia em lote
setInterval(() => {
  if (auditBuffer.length > 0) {
    const batch = auditBuffer.splice(0, auditBuffer.length);
    logger.info('auditoria_batch', { batch });
  }
}, FLUSH_INTERVAL);

// Amostragem para alto volume
function deveRegistrar() {
  // Registra apenas 10% das requisições em pico
  if (currentRPS > 1000) {
    return Math.random() < 0.1;
  }
  return true;
}

Referências

atados como Fluxos de Eventos](https://12factor.net/logs) — Princípio dos Doze Fatores que trata logs como streams, não arquivos.


Conclusão

Implementar auditoria de chamadas de API com logs estruturados é um passo fundamental para garantir conformidade, rastreabilidade e segurança em sistemas modernos. Ao longo deste artigo, percorremos desde os fundamentos da auditoria — entendendo a diferença entre logs tradicionais e estruturados — até a implementação prática com middlewares, bibliotecas de logging e ferramentas de armazenamento.

Vimos que um schema bem definido, com campos como correlation ID, usuário, ação e resultado, é a base para consultas eficientes e dashboards significativos. A escolha do backend de armazenamento depende do volume, da necessidade de consultas em tempo real e do orçamento disponível — Elasticsearch para análises complexas, Loki para baixo custo, ou S3 + Athena para dados históricos.

A segurança dos logs não pode ser negligenciada: dados sensíveis devem ser mascarados ou ofuscados antes do armazenamento, e o acesso aos logs precisa ser controlado com RBAC e políticas de imutabilidade. O impacto no desempenho pode ser mitigado com logging assíncrono, bufferização e amostragem inteligente em momentos de pico.

Ao adotar essas práticas, sua equipe terá visibilidade completa sobre o que acontece em suas APIs, poderá investigar incidentes com agilidade e estará preparada para auditorias externas e requisitos regulatórios. Lembre-se: logs não são apenas um subproduto do sistema — são um ativo estratégico para a operação e evolução do seu software.