
Um stack trace é a lista de chamadas de função que estavam em andamento quando algo lançou um erro. O navegador o escreve de dentro para fora: a função que falhou primeiro fica no topo, a que a chamou fica logo abaixo, e assim por diante até chegar a quem deu início a tudo.
Parece uma parede de texto e é tratado como tal. A maioria das pessoas lê a primeira linha, pesquisa a mensagem e nunca olha para o resto, o que é uma pena, porque a primeira linha diz o que quebrou e as linhas abaixo dizem por que alguém estava ali.
O que as linhas realmente são
Um trace de navegador se parece mais ou menos com isto:
TypeError: Cannot read properties of undefined (reading 'total')
at formatPrice (checkout.js:214:19)
at renderSummary (checkout.js:188:7)
at onCartLoaded (cart.js:97:5)
at HTMLButtonElement.<anonymous> (cart.js:41:12)
Há quatro coisas ali dentro, e cada uma responde a uma pergunta diferente.
A primeira linha é o erro: um tipo e uma mensagem. Ela diz o que deu errado no momento em que
deu errado, e é a linha menos útil para encontrar a causa. No exemplo, algo dentro de
formatPrice tentou ler .total de um valor que não estava lá, o que é o
caso da propriedade undefined
e quase nunca é culpa de formatPrice.
Cada linha at é um frame: uma função que estava esperando a de cima retornar. formatPrice
foi chamada por renderSummary, que foi chamada por onCartLoaded.
Os números são arquivo, linha e coluna. checkout.js:214:19 é a linha 214, coluna 19, o que
importa quando várias chamadas estão na mesma linha.
O frame de baixo é o gatilho, e costuma ser a linha isolada mais informativa de todo o trace.
HTMLButtonElement.<anonymous> significa que um manipulador de clique deu início a isso. Algo que
uma pessoa fez, e não algo que a página fez ao carregar. Isso já é um passo de reprodução.
Leia de baixo para cima
O hábito que vale a pena construir é ler o trace na ordem em que o código realmente rodou, ou seja, de baixo para cima.
Um clique disparou. onCartLoaded rodou. Ele chamou renderSummary, que chamou formatPrice,
que não encontrou nada onde esperava um total do carrinho. Lido assim, a pergunta deixa de ser “o
que há de errado com formatPrice” e passa a ser “o que onCartLoaded tinha em mãos, e de onde
veio isso?” É aí que a correção quase sempre está.
- Fraco
- Erro em formatPrice, linha 214. Adicionado um check para undefined.
- Melhor
- O manipulador de clique em cart.js:41 rodou antes da resposta do carrinho chegar, então renderSummary recebeu um objeto vazio. Corrigida a ordem; formatPrice ficou inalterado.
A primeira versão faz o erro desaparecer. A segunda faz o bug desaparecer. Não são o mesmo trabalho, e é o trace que diferencia um do outro.
Os frames que não são seus
A maioria dos traces reais é mais longa do que o exemplo e cheia de frames de bibliotecas: React, jQuery, o runtime de um bundler, um polyfill. Na maior parte do tempo são ruído, e os dois principais navegadores escondem isso para você.
No Chrome DevTools o recurso é a Ignore list, dentro de Settings, que recolhe frames de
scripts indicados por você e impede que o debugger entre neles. Por padrão ela já ignora
/node_modules/ e /bower_components/, então o primeiro movimento útil costuma ser adicionar
seus próprios caminhos de vendor a ela. O debugger do Firefox tem a mesma ideia, por arquivo, como
Ignore source. De um jeito ou de outro, todo trace daí em diante fica com metade do tamanho.
O que sobra é o seu próprio código, que é a parte que você pode mudar.
Uma exceção que vale a pena vigiar: se os únicos frames forem de bibliotecas, a chamada provavelmente veio de dentro dessa biblioteca, e não do seu código, e a causa costuma ser os dados ou as opções que você passou a ela.
Por que o trace às vezes aponta para algo sem sentido
Três coisas costumam tornar um trace ilegível, e as três dizem respeito a como o código foi entregue, não ao bug em si.
Minificação. Em produção seu código vira uma linha enorme com nomes de uma letra só, então o
trace mostra at n (app.min.js:1:84213). O número da linha é real e inútil. A solução são os
source maps: o build gera um arquivo .map, o navegador o aplica, e o DevTools mostra os nomes de
arquivo e as linhas originais. Verifique se os seus estão sendo gerados, e se o navegador consegue
acessá-los, antes de concluir que um trace não tem mais jeito.
Limites assíncronos. Um trace para onde a call stack para, e um setTimeout, um callback de
promise ou um event listener inicia uma nova. O frame que agendou o trabalho não entra no trace
por padrão. O recurso Async stack traces do Chrome vem ativado no DevTools e costura os dois
juntos, motivo pelo qual um trace parece mais completo no DevTools do que o mesmo erro num arquivo
de log.
Scripts cross-origin. Se um script é servido a partir de outro domínio sem permissão para
expor seus erros, o navegador se recusa a dizer qualquer coisa sobre ele: a mensagem vira a string
crua Script error., sem arquivo, sem linha e sem trace. A correção fica do lado de quem serve o
script: Access-Control-Allow-Origin no script, mais crossorigin="anonymous" na tag. Vale saber
porque o sintoma parece um erro misterioso e na verdade é uma regra de permissão.
O trace que você não coletou
Tudo acima presume que você tem o trace. O caso incômodo é o que mais importa: o erro aconteceu na máquina de outra pessoa, num navegador que você não tem, e o que chegou até você foi uma frase.
Um stack trace não é algo que dá para pedir a um usuário. Ele vive num painel que essa pessoa nunca abriu, some quando a aba é fechada, e o console não sobrevive a uma navegação de página a menos que alguém tenha pensado em marcar Preserve log antes. Então a escolha prática é entre ter um error tracker ou uma ferramenta de report que capture o console no momento da falha, ou ficar sem nada disso.
Sem isso, o relato diz “a página de checkout ficou em branco”, e as quatro linhas que teriam respondido a isso em um minuto foram descartadas pelo navegador segundos depois.
Session Replay
Extensão gratuita para Chrome. Um clique na página que está com problema captura a tela, o console e o log de rede, e entrega um link para colar no ticket.
Gerando um de propósito
Vale a pena saber duas coisas para quando você quer um trace em vez de esperar por um.
console.trace() imprime a call stack atual sem lançar nada, o que é a forma mais rápida de
responder “quem está chamando essa função?” numa base de código que você não conhece.
new Error().stack dá a mesma coisa como string, que é o que os error trackers enviam para o
servidor. É também assim que você anexa um trace a um erro capturado que, de outra forma,
desapareceria: capturar uma exceção e logar somente e.message descarta a call stack inteira, e
esse hábito é responsável por boa parte dos logs de produção ilegíveis.
O que fica disso
A linha do topo nomeia o sintoma. A linha de baixo nomeia o que deu início a tudo. O meio é o caminho entre as duas, e a correção geralmente está em algum ponto desse caminho, não em uma das pontas.
Leia de baixo para cima, esconda os frames que não são seus, e verifique seus source maps antes de culpar o trace. E se o trace de que você precisa pertence à sessão de navegador de outra pessoa, o momento de se preparar para isso é antes do bug, não depois: quando o relato chega, ele já sumiu.