Como depurar problemas de rede em aplicações Node
Problemas de rede em aplicações Node.js podem ser particularmente desafiadores devido à natureza assíncrona e orientada a eventos da plataforma. Este artigo apresenta um guia prático para diagnosticar e resolver os problemas de rede mais comuns, desde o rastreamento básico de requisições até a análise avançada de tráfego.
1. Diagnóstico Inicial com Ferramentas Nativas do Node
Antes de recorrer a ferramentas externas, o Node oferece recursos nativos poderosos para depuração de rede. O módulo util com inspect permite visualizar objetos complexos de requisições HTTP:
const http = require('http');
const util = require('util');
const server = http.createServer((req, res) => {
console.log('Requisição recebida:');
console.log(util.inspect(req.headers, { showHidden: true, depth: null }));
res.end('OK');
});
server.listen(3000);
Para ativar logs detalhados de rede, use a variável de ambiente NODE_DEBUG:
NODE_DEBUG=net,http node app.js
Isso exibirá todas as operações de socket e requisições HTTP, incluindo conexões abertas, dados enviados/recebidos e eventos de fechamento.
2. Análise de Timeouts e Conexões Pendentes
Timeouts mal configurados são uma das principais causas de problemas de rede. O Node permite configurar timeouts em múltiplos níveis:
const server = http.createServer((req, res) => {
// Timeout por requisição
req.setTimeout(5000, () => {
console.error('Timeout da requisição');
res.statusCode = 408;
res.end('Request Timeout');
});
// Simula processamento lento
setTimeout(() => {
res.end('Processado');
}, 10000);
});
server.timeout = 10000; // Timeout global do servidor
server.listen(3000);
Para detectar sockets vazados (não encerrados), utilize process._getActiveHandles():
setInterval(() => {
const handles = process._getActiveHandles();
const sockets = handles.filter(h => h.constructor.name === 'Socket');
console.log(`Sockets ativos: ${sockets.length}`);
sockets.forEach((s, i) => {
console.log(`Socket ${i}: ${s.remoteAddress}:${s.remotePort}`);
});
}, 5000);
Sempre encerre sockets corretamente em caso de erro:
socket.on('error', (err) => {
console.error('Erro no socket:', err.message);
socket.destroy(); // Garante o fechamento
});
3. Depuração de DNS e Resolução de Hostnames
Problemas de DNS podem causar falhas intermitentes difíceis de diagnosticar. Teste a resolução diretamente com o módulo nativo dns:
const dns = require('dns');
// Teste de resolução
dns.resolve('api.exemplo.com', 'A', (err, addresses) => {
if (err) {
console.error('Falha na resolução DNS:', err.code);
// Fallback para IP estático
console.log('Usando IP de fallback: 192.168.1.100');
} else {
console.log('Endereços resolvidos:', addresses);
}
});
// Configurar servidores DNS personalizados
dns.setServers(['8.8.8.8', '1.1.1.1']);
Para cache de DNS, implemente um mecanismo simples:
const dnsCache = new Map();
const cacheTTL = 300000; // 5 minutos
function resolveWithCache(hostname) {
const cached = dnsCache.get(hostname);
if (cached && (Date.now() - cached.timestamp) < cacheTTL) {
return Promise.resolve(cached.address);
}
return new Promise((resolve, reject) => {
dns.resolve(hostname, (err, addresses) => {
if (err) reject(err);
else {
dnsCache.set(hostname, { address: addresses[0], timestamp: Date.now() });
resolve(addresses[0]);
}
});
});
}
4. Rastreamento de Requisições HTTP/HTTPS com Interceptação
Para depurar chamadas HTTP sem modificar o código original, crie um proxy de interceptação:
const http = require('http');
// Proxy de interceptação
const proxy = http.createServer((req, res) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
console.log('Headers:', JSON.stringify(req.headers, null, 2));
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
if (body) console.log('Body:', body);
// Encaminha para o servidor real
const options = {
hostname: 'api.exemplo.com',
port: 80,
path: req.url,
method: req.method,
headers: req.headers
};
const proxyReq = http.request(options, (proxyRes) => {
console.log('Status:', proxyRes.statusCode);
res.writeHead(proxyRes.statusCode, proxyRes.headers);
proxyRes.pipe(res);
});
proxyReq.write(body);
proxyReq.end();
});
});
proxy.listen(8080, () => {
console.log('Proxy de depuração rodando na porta 8080');
});
5. Monitoramento de Tráfego com Ferramentas Externas
Para análise avançada, integre o Node com ferramentas como tcpdump. Crie um script que capture tráfego específico:
const { exec } = require('child_process');
function captureTraffic(port, duration = 30) {
const cmd = `sudo tcpdump -i any port ${port} -w captura_${port}.pcap`;
console.log(`Capturando tráfego na porta ${port} por ${duration}s...`);
const capture = exec(cmd);
setTimeout(() => {
capture.kill();
console.log('Captura concluída. Arquivo: captura_' + port + '.pcap');
console.log('Analise com: wireshark captura_' + port + '.pcap');
}, duration * 1000);
}
// Uso: captureTraffic(3000, 60);
Para testes isolados, integre chamadas curl no Node:
const { execSync } = require('child_process');
function testEndpoint(url) {
try {
const result = execSync(`curl -v --connect-timeout 5 --max-time 10 ${url}`, {
encoding: 'utf8',
maxBuffer: 1024 * 1024
});
console.log('Resposta:', result);
} catch (error) {
console.error('Falha na requisição:', error.stderr);
}
}
testEndpoint('http://localhost:3000/api/health');
6. Debug de WebSockets e Conexões Persistentes
WebSockets exigem atenção especial devido à natureza persistente da conexão:
const WebSocket = require('ws');
const ws = new WebSocket('ws://localhost:8080');
ws.on('open', () => {
console.log('[WS] Conexão estabelecida');
console.log('[WS] Protocolo:', ws.protocol);
console.log('[WS] Extensões:', ws.extensions);
});
ws.on('message', (data) => {
console.log('[WS] Mensagem recebida:', data.toString());
});
ws.on('close', (code, reason) => {
console.log(`[WS] Conexão fechada - Código: ${code}, Razão: ${reason}`);
// Reconexão automática
setTimeout(() => {
console.log('[WS] Tentando reconectar...');
// Lógica de reconexão
}, 5000);
});
ws.on('error', (error) => {
console.error('[WS] Erro:', error.message);
console.error('[WS] Stack:', error.stack);
});
7. Isolamento de Problemas com Containers e Load Balancers
Para simular falhas de rede em ambientes containerizados, use tc (traffic control) via Node:
const { execSync } = require('child_process');
function simulateLatency(containerName, latencyMs = 200) {
const cmd = `docker exec ${containerName} tc qdisc add dev eth0 root netem delay ${latencyMs}ms`;
try {
execSync(cmd);
console.log(`Latência de ${latencyMs}ms aplicada ao container ${containerName}`);
} catch (error) {
console.error('Erro ao simular latência:', error.message);
}
}
function simulatePacketLoss(containerName, lossPercent = 10) {
const cmd = `docker exec ${containerName} tc qdisc add dev eth0 root netem loss ${lossPercent}%`;
try {
execSync(cmd);
console.log(`Perda de ${lossPercent}% aplicada ao container ${containerName}`);
} catch (error) {
console.error('Erro ao simular perda:', error.message);
}
}
// Uso: simulateLatency('api-container', 500);
Configure corretamente o http.Agent para gerenciar conexões:
const http = require('http');
const agent = new http.Agent({
keepAlive: true,
keepAliveMsecs: 1000,
maxSockets: 50,
maxFreeSockets: 10,
timeout: 60000
});
const options = {
hostname: 'api.exemplo.com',
port: 80,
path: '/',
agent: agent
};
const req = http.get(options, (res) => {
console.log('Conexão reutilizada:', res.socket.isSessionReused());
});
8. Automação de Diagnósticos com Scripts e Logs Estruturados
Crie um script de monitoramento contínuo que gera logs JSON para integração com ELK:
const http = require('http');
const fs = require('fs');
function monitorEndpoint(url, interval = 5000) {
const logStream = fs.createWriteStream('network_monitor.log', { flags: 'a' });
setInterval(() => {
const start = Date.now();
const logEntry = {
timestamp: new Date().toISOString(),
url: url,
type: 'health_check'
};
http.get(url, (res) => {
const duration = Date.now() - start;
logEntry.status = res.statusCode;
logEntry.duration_ms = duration;
logEntry.success = res.statusCode >= 200 && res.statusCode < 400;
logStream.write(JSON.stringify(logEntry) + '\n');
console.log(`[${logEntry.timestamp}] ${url} - ${logEntry.status} (${duration}ms)`);
}).on('error', (err) => {
logEntry.status = 0;
logEntry.error = err.message;
logEntry.success = false;
logStream.write(JSON.stringify(logEntry) + '\n');
console.error(`[${logEntry.timestamp}] ${url} - ERRO: ${err.message}`);
});
}, interval);
}
monitorEndpoint('http://localhost:3000/health');
Para debugging remoto com breakpoints condicionais:
// Inicie com: node --inspect app.js
// Depois acesse: chrome://inspect
function processRequest(data) {
// Breakpoint condicional: data.statusCode >= 500
if (data.statusCode >= 500) {
debugger; // Só para quando houver erro 5xx
console.error('Erro no servidor:', data);
}
// Lógica normal
return data;
}
Referências
- Node.js Documentation: Debugging Guide — Guia oficial do Node.js sobre técnicas de debugging, incluindo o uso do inspector e variáveis de ambiente NODE_DEBUG.
- Node.js HTTP Module Documentation — Documentação completa do módulo HTTP do Node.js, com detalhes sobre timeouts, agentes e gerenciamento de sockets.
- Debugging Node.js Applications with Chrome DevTools — Artigo técnico sobre como usar o Chrome DevTools para depurar aplicações Node.js remotamente.
- WebSocket API Documentation (MDN) — Documentação da Mozilla sobre WebSockets, incluindo eventos, handshake e boas práticas de reconexão.
- Using tcpdump for Network Debugging — Guia prático da Red Hat sobre uso do tcpdump para captura e análise de tráfego de rede.
- Docker Network Troubleshooting Guide — Documentação oficial do Docker sobre diagnóstico de problemas de rede em containers, incluindo uso de tc e netcat.
- ELK Stack Documentation for Node.js Logs — Guia da Elastic sobre como estruturar logs Node.js em JSON para integração com a stack ELK.