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
- Documentação Oficial do Pino — Logging Estruturado para Node.js — Biblioteca de logging de alta performance com saída JSON, ideal para auditoria de APIs.
- Elastic Common Schema (ECS) — Schema Padrão para Logs — Definição oficial de campos para logs estruturados, usado por Elasticsearch e Logstash.
- OWASP Logging Cheat Sheet — Boas Práticas de Segurança em Logs — Guia da OWASP sobre o que logar, o que evitar e como proteger dados sensíveis.
- Grafana Loki — Documentação de Armazenamento e Consulta de Logs — Sistema de agregação de logs otimizado para baixo custo e integração com dashboards.
- AWS Well-Architected Framework — Logging e Monitoramento — Práticas recomendadas para armazenamento, retenção e análise de logs em nuvem.
- [12 Factor App — Logs Tr
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.