SkillsTecnológicas
Menu
Back-end

Como Depurar Aplicações Node.js: Técnicas e Ferramentas

Aprenda a depurar aplicações Node.js com Inspector, breakpoints, stack traces, logs estruturados, source maps e diagnósticos de memória e CPU.

Skills TecnológicasPublicado em 7 de março de 2025Atualizado em 19 de agosto de 202612 min de leitura
Aplicação de servidor é inspecionada por instrumentos de diagnóstico que destacam uma falha em seu fluxo de execução

Depurar aplicações Node.js é investigar por que o comportamento observado difere do esperado, localizar a causa e provar que a correção resolve o problema sem criar uma regressão.

O processo pode começar em uma mensagem de erro, mas raramente termina nela: contexto da requisição, pilha de chamadas, estado das variáveis, uso de memória e dependências externas também contam a história.

A ferramenta certa depende do sintoma. Logs ajudam a reconstruir eventos; breakpoints mostram o estado exato de uma execução; perfis revelam gargalos de CPU; snapshots e relatórios apoiam a análise de memória. Usar tudo ao mesmo tempo cria ruído.

O caminho mais rápido é formular uma hipótese, escolher a evidência capaz de confirmá-la e avançar em ciclos curtos.

Neste guia, você aprenderá um fluxo reproduzível para depuração local e em produção, com Node Inspector, breakpoints, stack traces, source maps, logs estruturados, contexto assíncrono e relatórios de diagnóstico.

O que significa depurar uma aplicação Node.js?

Depuração é um processo de investigação, não um sinônimo de adicionar console.log. Você observa um desvio, reúne evidências, formula uma hipótese, executa um teste controlado e compara o resultado.

Se a hipótese estiver errada, a evidência reduz o espaço de busca; se estiver correta, você ainda precisa validar a correção.

Sintoma, causa e correção são coisas diferentes

Uma resposta HTTP 500 é um sintoma. A causa pode ser uma variável nula, um timeout no banco ou uma promise rejeitada.

Reiniciar o processo pode remover o sintoma por alguns minutos, mas não corrige um vazamento de memória. Registre separadamente o que ocorreu, por que ocorreu e qual alteração impede a repetição.

Comece por um caso reproduzível

Descreva a versão do Node.js, sistema operacional, variáveis relevantes, entrada, resultado esperado e resultado real.

Depois transforme a descrição em uma sequência curta. “O endpoint falha às vezes” é pouco acionável; “duas requisições simultâneas com o mesmo identificador geram uma resposta sem corpo” já aponta para concorrência e estado compartilhado.

Reduza entradas e dependências

Remova campos da entrada, substitua temporariamente integrações por respostas controladas e execute a menor parte capaz de apresentar a falha. Se o problema desaparecer ao retirar o acesso ao banco, o limite da investigação mudou.

Essa redução também facilita transformar o caso em um teste automatizado.

Leia o erro e a stack trace

A stack trace registra os frames percorridos até a criação do erro. Comece pelo primeiro frame que pertence ao seu projeto, mas não ignore a mensagem, o tipo, o código e a causa.

A documentação de erros do Node.js distingue erros JavaScript, erros de sistema e exceções produzidas pelas APIs da plataforma.

try {
  return await repositorio.buscar(id);
} catch (erro) {
  throw new Error(`Falha ao carregar o pedido ${id}`, { cause: erro });
}

Prefira error.code a comparar mensagens

Mensagens podem mudar entre versões e incluir valores variáveis. Quando uma API fornece error.code, use esse identificador para decidir o tratamento.

Por exemplo, diferenciar “arquivo inexistente” de “permissão negada” pelo código é mais robusto do que buscar uma frase dentro de error.message.

Preserve a causa original do erro

Ao adicionar contexto de negócio, preserve o erro original em cause. Assim, o log pode informar qual pedido falhou sem apagar o código ou a pilha produzidos pelo driver.

Evite capturar uma exceção apenas para lançar outra genérica.

Escolha a ferramenta pelo sintoma

SintomaEvidência inicialFerramenta indicada
Exceção reproduzívelEstado antes da falhaBreakpoint e stack trace
Falha intermitenteSequência de eventosLogs estruturados e correlação
Processo cada vez maiorEvolução do heapMétricas e heap snapshot
CPU alta ou resposta lentaFunções mais custosasCPU profile
Queda sem reproduçãoEstado do processoRelatório de diagnóstico

Uma ferramenta mede algo específico. Um breakpoint pode explicar uma condição lógica, mas altera o tempo da execução e pode esconder uma corrida. Um log mostra eventos reais, porém só revela os campos que você decidiu registrar.

Ative o Node Inspector

O Node.js inclui um cliente de depuração e integração com ferramentas compatíveis com o protocolo do V8 Inspector.

Para uma sessão no terminal, execute node inspect app.js. Para conectar uma interface gráfica, inicie o processo com uma das opções documentadas no depurador oficial do Node.js.

node --inspect app.js
node --inspect-brk app.js
node --inspect-wait app.js

inspect, inspect-brk e inspect-wait

--inspect inicia a aplicação e aceita uma conexão. --inspect-brk pausa antes do código do usuário, útil para investigar a inicialização. --inspect-wait aguarda o depurador se conectar antes de continuar.

Escolha conscientemente para não perder uma falha que ocorre logo no bootstrap.

Não exponha a porta de depuração

Quem acessa a porta do Inspector pode executar ações com os privilégios do processo. Mantenha o endereço vinculado a uma interface local e use túnel seguro quando houver necessidade real de acesso remoto.

Nunca publique a porta diretamente na internet nem a deixe permanentemente aberta em produção.

Configure o depurador no VS Code

O VS Code pode iniciar o arquivo pelo depurador ou anexar-se a um processo já executado com Inspector. Uma configuração mínima em .vscode/launch.json mantém o comando reproduzível para a equipe:

{
  "version": "0.2.0",
  "configurations": [{
    "type": "node",
    "request": "launch",
    "name": "Depurar API",
    "program": "${workspaceFolder}/src/server.js",
    "envFile": "${workspaceFolder}/.env.development"
  }]
}

Use launch ou attach?

Use launch quando o editor deve controlar o ciclo de vida do processo. Prefira attach quando a aplicação é iniciada por um script, container ou gerenciador.

Em ambos os casos, confirme se o arquivo executado e os source maps correspondem ao código aberto no editor.

Fluxo de execução pausado em um breakpoint revela camadas da pilha de chamadas e valores usados pela aplicação
Breakpoints interrompem a execução no ponto certo para inspecionar a pilha de chamadas, os valores em memória e o caminho percorrido pelo código.

Domine breakpoints e a pilha de chamadas

Coloque o breakpoint na decisão que separa o comportamento correto do incorreto, não necessariamente na linha que lança o erro.

Ao pausar, examine a call stack de baixo para cima: ela mostra quem chamou a função, quais caminhos foram executados e onde um valor inesperado entrou no fluxo.

Breakpoints condicionais e logpoints

Em loops ou endpoints movimentados, uma pausa a cada passagem é impraticável. Um breakpoint condicional interrompe apenas quando uma expressão é verdadeira, como pedido.id === idInvestigado.

Logpoints registram valores sem adicionar uma chamada permanente ao código, embora também possam influenciar o tempo em cenários muito sensíveis.

Inspecione valores sem alterar o fluxo

Use os painéis de variáveis, watch e escopo para comparar parâmetros, variáveis locais e closures.

Evite executar expressões com efeitos colaterais no console de depuração: chamar uma função que grava no banco ou consome uma fila muda o sistema que você está tentando observar.

Depure promises e código assíncrono

Em Node.js, a pilha síncrona termina enquanto operações de rede, arquivos e timers continuam. Isso torna a ordem dos eventos tão importante quanto o valor final.

Revise o fluxo com base em async, await e promises e confirme onde cada promise é criada, aguardada e tratada.

Procure await ausente e concorrência acidental

Um await ausente pode devolver uma promise em vez do resultado, encerrar a resposta cedo ou deixar uma rejeição escapar. Já um Promise.all pode iniciar todas as operações simultaneamente e sobrecarregar uma dependência.

Verifique também callbacks dentro de forEach, pois o método não espera o retorno assíncrono de cada iteração.

Trate rejeições no limite correto

Capture o erro onde existe contexto para decidir: tentar novamente, responder ao cliente, compensar uma operação ou encerrar o processo.

Um handler global serve como última barreira e fonte de diagnóstico, não como mecanismo para fingir que a aplicação continua saudável após um estado desconhecido.

Habilite source maps para TypeScript

Sem source maps, a stack trace aponta para JavaScript compilado em dist, não para o arquivo TypeScript que você editou. Gere mapas no compilador e execute node --enable-source-maps dist/server.js.

A opção oficial de source maps reconstrói as posições originais nas traces; ela também pode acrescentar latência ao acessar Error.stack, então meça o impacto em rotas muito críticas.

Crie logs úteis para investigação

Um log útil responde o que aconteceu, quando, em qual componente e sob qual contexto. Prefira eventos estruturados a frases concatenadas.

Campos como nível, nome do evento, duração, rota, código do erro e identificadores pesquisáveis tornam a investigação possível sem depender de leitura manual linha a linha.

logger.error({
  event: 'pedido_busca_falhou',
  requestId,
  pedidoId: id,
  errorCode: erro.code,
  err: erro
});

Use IDs de correlação

Um identificador por requisição permite unir logs do controller, serviço, banco e chamadas externas.

A classe AsyncLocalStorage do Node.js propaga um contexto pelas operações assíncronas, evitando passar requestId manualmente por todas as funções. Integre o valor também a traces distribuídos quando o sistema tiver vários serviços.

Proteja segredos e dados pessoais

Tokens, senhas, cookies, documentos e corpos completos de requisições não devem aparecer por conveniência. Crie uma lista de campos mascarados, limite o tamanho das entradas e defina retenção.

Um log de depuração que resolve um bug, mas vaza credenciais, produz um incidente maior.

Serviços em nuvem são observados por camadas de rastreamento de requisições, memória e uso de processamento
Em produção, IDs de correlação, métricas e relatórios de diagnóstico ajudam a relacionar uma falha ao consumo de memória, CPU e dependências externas.

Investigue erros que só aparecem em produção

Não reproduzir localmente não significa ausência de padrão. Consulte frequência, versão implantada, instância, horário, rota, cliente, volume e dependência envolvida.

Use logs, métricas e traces para construir um caso representativo em ambiente isolado. Se a aplicação é gerenciada por processo, entenda também como executar aplicações com PM2 e preservar os registros durante reinícios.

Compare ambiente e carga

Compare versão do runtime, dependências, arquitetura, limites de memória, timezone, locale, configuração de proxy e variáveis não secretas.

Depois avalie concorrência, tamanho dos dados e latência das integrações. Muitos “bugs de produção” são comportamentos que só emergem sob carga ou diferenças de configuração.

Diagnostique vazamentos de memória

Observe a memória por vários ciclos de carga e coleta de lixo. Crescimento isolado não prova vazamento; o sinal relevante é uma linha de base que permanece cada vez maior.

Causas frequentes incluem caches sem limite, listeners não removidos, timers, closures que retêm objetos e resultados grandes mantidos em variáveis globais.

const memoria = process.memoryUsage();
logger.info({
  rss: memoria.rss,
  heapUsed: memoria.heapUsed,
  external: memoria.external
});

Use snapshots com cautela

Um heap snapshot permite comparar tipos de objetos, caminhos de retenção e dominadores, mas a coleta pode pausar a aplicação e consumir memória adicional. Gere-o em uma réplica ou janela controlada, proteja o arquivo porque ele pode conter dados sensíveis e compare pelo menos dois momentos equivalentes.

Encontre gargalos de CPU e event loop

Como o JavaScript da aplicação normalmente executa no event loop, uma função síncrona longa atrasa outras requisições. Antes de otimizar, capture um perfil de CPU sob carga representativa e procure funções que concentram tempo. JSON muito grande, regex custosa, compressão, criptografia e loops de transformação são candidatos comuns. Para uma visão mais ampla, conecte métricas e traces com uma estratégia de observabilidade usando OpenTelemetry.

Gere relatórios de diagnóstico

Os relatórios de diagnóstico do Node.js registram informações do processo, stack, heap, recursos e plataforma em JSON. Eles podem ser gerados por sinal, exceção fatal ou chamada como process.report.writeReport(). Configure o destino em área protegida e considere excluir variáveis de ambiente, porque o relatório pode conter informações sensíveis.

process.report.directory = '/var/app/diagnostics';
const arquivo = process.report.writeReport();
logger.warn({ event: 'diagnostic_report_created', arquivo });

Erros comuns durante a depuração

  • alterar várias partes do código antes de testar uma hipótese;
  • registrar objetos inteiros sem limite ou mascaramento;
  • ignorar o primeiro erro e investigar apenas falhas em cascata;
  • reproduzir com versões ou dados diferentes dos usados no incidente;
  • manter o Inspector exposto em um servidor;
  • concluir que existe vazamento após uma única medição de memória;
  • corrigir o sintoma sem criar um teste de regressão;
  • culpar o ORM antes de medir a consulta e a conexão — revise a camada de dados e os ORMs em aplicações Node.js.

Fluxo prático para corrigir um bug

  1. Defina o desvio: esperado, observado, impacto e frequência.
  2. Preserve evidências: erro completo, correlação, versão e condições.
  3. Reproduza: reduza o cenário até obter uma sequência confiável.
  4. Localize o limite: entrada, regra, banco, rede ou infraestrutura.
  5. Formule uma hipótese: escreva o mecanismo causal que explicaria o sintoma.
  6. Escolha uma medida: breakpoint, log, perfil, snapshot ou relatório.
  7. Faça uma alteração mínima: evite misturar refatoração com correção.
  8. Crie um teste de regressão: ele deve falhar antes e passar depois.
  9. Valide efeitos colaterais: desempenho, segurança e fluxos vizinhos.
  10. Monitore após implantar: confirme a queda do erro e a saúde do serviço.

Checklist antes de encerrar a investigação

  • a causa explica todos os sintomas observados;
  • o cenário é reproduzível ou sustentado por evidência de produção;
  • a correção é menor e mais direta que uma reescrita ampla;
  • existe teste automatizado ou procedimento de validação;
  • logs temporários e portas de depuração foram removidos;
  • segredos e dados pessoais não foram incluídos nos artefatos;
  • dashboards ou alertas conseguem confirmar o resultado após a implantação;
  • a decisão e seus limites foram documentados para a equipe.

Perguntas frequentes

console.log é suficiente para depurar Node.js?

É suficiente para hipóteses simples e locais, mas não para todos os casos. Em produção, logs estruturados com nível, timestamp e correlação são mais pesquisáveis. Para estado interno, CPU ou memória, use breakpoints, perfis e diagnósticos adequados.

Posso usar o Inspector em produção?

Somente em uma situação controlada, por tempo curto e com acesso estritamente protegido. Pausas afetam requisições, e a porta permite controle poderoso sobre o processo. Prefira observabilidade, relatórios e reprodução em réplica sempre que possível.

Como depurar uma aplicação executada pelo PM2?

Primeiro identifique a instância, versão e logs correspondentes. Para depuração interativa, reproduza em ambiente isolado e inicie uma única instância com os argumentos do Inspector. Em cluster, cada processo precisa de porta própria; não exponha essas portas e restaure a configuração normal ao terminar.

Conclusão

Depurar aplicações Node.js fica mais previsível quando a investigação parte de um sintoma preciso, reduz o cenário e mede uma hipótese de cada vez.

Stack traces e breakpoints esclarecem erros locais; source maps devolvem contexto ao TypeScript; logs correlacionados, perfis e relatórios revelam problemas que dependem de carga ou ambiente.

A correção só está completa quando a causa foi demonstrada, existe proteção contra regressão e o comportamento em produção confirma o resultado.

Esse ciclo transforma depuração de tentativa e erro em uma prática técnica repetível.