<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alexandre Guimarães Filho</title>
    <description>The latest articles on DEV Community by Alexandre Guimarães Filho (@alexandre_guimaresfilho).</description>
    <link>https://dev.to/alexandre_guimaresfilho</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4050328%2F54fb6d75-c51a-4d9c-9f1c-a244b21dcaa7.jpg</url>
      <title>DEV Community: Alexandre Guimarães Filho</title>
      <link>https://dev.to/alexandre_guimaresfilho</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alexandre_guimaresfilho"/>
    <language>en</language>
    <item>
      <title>O alerta que dizia Error: {}</title>
      <dc:creator>Alexandre Guimarães Filho</dc:creator>
      <pubDate>Tue, 28 Jul 2026 02:03:10 +0000</pubDate>
      <link>https://dev.to/alexandre_guimaresfilho/o-alerta-que-dizia-error--2a8h</link>
      <guid>https://dev.to/alexandre_guimaresfilho/o-alerta-que-dizia-error--2a8h</guid>
      <description>&lt;p&gt;Caiu um alerta avisando que uma integração tinha falhado. Fui ver o que era:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Integration failed
Error: {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Error: {}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Só isso. Duas chaves. O sistema avisou que algo quebrou e, no mesmo movimento, decidiu não contar o quê.&lt;/p&gt;

&lt;p&gt;E o pior é que o alerta &lt;em&gt;funcionou&lt;/em&gt;. Disparou na hora certa, chegou onde devia, alguém leu. Cumpriu todas as etapas, menos a única que importava.&lt;/p&gt;

&lt;p&gt;O reflexo aqui — e eu já fiz isso — é reprocessar o que falhou e tocar o dia. Deu certo, ninguém reclamou, bola pra frente. Só que tem uma conta que a gente não faz nessa hora: &lt;strong&gt;se o alerta não diz nada hoje, ele também não vai dizer nada da próxima vez.&lt;/strong&gt; E vai ter próxima vez.&lt;/p&gt;

&lt;p&gt;Então vale parar e tratar o alerta vazio como o bug. Porque ele é.&lt;/p&gt;

&lt;p&gt;Este texto tem duas partes. A primeira é por que isso acontece — e a resposta é mais irritante do que parece. A segunda é o tipo de bug que esse log costuma estar cobrindo, que no meu caso era um retry que eu jurava estar funcionando.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por que a mensagem some
&lt;/h2&gt;

&lt;p&gt;Cabe em duas linhas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timeout of 30000ms exceeded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// '{}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Não é bug do Node. Não é bug do seu logger. É a especificação da linguagem fazendo exatamente o que foi escrito pra fazer.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;JSON.stringify&lt;/code&gt; só serializa &lt;strong&gt;propriedades próprias e enumeráveis&lt;/strong&gt;. E num &lt;code&gt;Error&lt;/code&gt;, tanto &lt;code&gt;message&lt;/code&gt; quanto &lt;code&gt;stack&lt;/code&gt; são não-enumeráveis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                                          &lt;span class="c1"&gt;// []&lt;/span&gt;
&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOwnPropertyDescriptor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;enumerable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOwnPropertyDescriptor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stack&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;enumerable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Object.keys&lt;/code&gt; num erro devolve array vazio. O objeto tem mensagem, tem stack trace, tem tudo — e do ponto de vista do &lt;code&gt;JSON.stringify&lt;/code&gt;, está vazio.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pausa: o que é "não-enumerável"?
&lt;/h3&gt;

&lt;p&gt;Toda propriedade em JavaScript carrega uma flag chamada &lt;code&gt;enumerable&lt;/code&gt;, que responde uma pergunta só: &lt;strong&gt;"quando alguém percorrer as propriedades deste objeto, eu apareço na lista?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Não é sobre existir. É sobre aparecer quando alguém varre o objeto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;nome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ana&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;senha&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;123456&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;enumerable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// ← a única diferença&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;senha&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;              &lt;span class="c1"&gt;// '123456'   — existe e funciona normalmente&lt;/span&gt;
&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;// ['nome']   — mas não aparece na lista&lt;/span&gt;
&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    &lt;span class="c1"&gt;// '{"nome":"Ana"}'&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;senha&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="c1"&gt;// true       — e ainda assim está lá&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A propriedade está no objeto. Ela só não se apresenta quando chamam a lista.&lt;/p&gt;

&lt;p&gt;Isso existe por um motivo razoável: se tudo fosse enumerável, um &lt;code&gt;for...in&lt;/code&gt; qualquer listaria também &lt;code&gt;toString&lt;/code&gt;, &lt;code&gt;hasOwnProperty&lt;/code&gt; e toda a herança do objeto. Então a linguagem marca como não-enumerável o que é &lt;strong&gt;encanamento&lt;/strong&gt;, não dado — e decidiu que &lt;code&gt;message&lt;/code&gt; e &lt;code&gt;stack&lt;/code&gt; são encanamento do &lt;code&gt;Error&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;O problema é quem segue essa regra:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;vê enumeráveis&lt;/th&gt;
&lt;th&gt;vê não-enumeráveis&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Object.keys()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;for...in&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;JSON.stringify()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;{...spread}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Object.getOwnPropertyNames()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;acesso direto (&lt;code&gt;err.message&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As quatro primeiras linhas explicam este artigo inteiro. E dá pra ver os dois mundos lado a lado no mesmo objeto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                  &lt;span class="c1"&gt;// []&lt;/span&gt;
&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOwnPropertyNames&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// ['stack', 'message']&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Guarda essa tabela, porque ela volta a importar daqui a pouco.&lt;/p&gt;

&lt;p&gt;O que torna isso especialmente cruel é que &lt;strong&gt;em todo lugar, menos no JSON, funciona.&lt;/strong&gt; &lt;code&gt;err.message&lt;/code&gt; te dá a mensagem. &lt;code&gt;console.error(err)&lt;/code&gt; imprime o stack inteiro, bonitinho. Você testa no terminal, vê que está tudo lá, conclui que o log está bem. Aí vai pra produção, onde o log passa por JSON no caminho — e some.&lt;/p&gt;

&lt;p&gt;E some do jeito mais silencioso possível. Se virasse &lt;code&gt;undefined&lt;/code&gt;, seu olho pegava. &lt;code&gt;{}&lt;/code&gt; parece dado. Parece que tem alguma coisa ali.&lt;/p&gt;

&lt;h2&gt;
  
  
  Onde isso pega você
&lt;/h2&gt;

&lt;p&gt;Quase ninguém escreve &lt;code&gt;JSON.stringify(err)&lt;/code&gt; diretamente. O que a gente escreve é isto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;falha ao chamar serviço externo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                       &lt;span class="c1"&gt;// ← esse aqui&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Log estruturado, campos organizados, tudo certo. E o resultado é este:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;falhou&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;boom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// '{"msg":"falhou","error":{}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repare: &lt;strong&gt;todo o resto serializou perfeitamente.&lt;/strong&gt; O &lt;code&gt;msg&lt;/code&gt; está lá, o &lt;code&gt;orderId&lt;/code&gt; estaria lá, o &lt;code&gt;provider&lt;/code&gt; também. O único campo que morreu foi justamente o que você abriria primeiro.&lt;/p&gt;

&lt;h3&gt;
  
  
  O caso pior é quando sobra alguma coisa
&lt;/h3&gt;

&lt;p&gt;Se você usa &lt;code&gt;axios&lt;/code&gt; — ou qualquer lib que pendura propriedades no erro — segura, que aqui piora:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Request failed with status code 500&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ERR_BAD_RESPONSE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.exemplo.com/pedidos&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// '{"code":"ERR_BAD_RESPONSE","config":{"url":"https://api.exemplo.com/pedidos"}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Olha o que aconteceu. &lt;code&gt;code&lt;/code&gt; e &lt;code&gt;config&lt;/code&gt; foram anexados &lt;strong&gt;depois&lt;/strong&gt; que o erro nasceu — e atribuição normal (&lt;code&gt;err.code = ...&lt;/code&gt;) cria a propriedade como enumerável, que é o padrão. Então elas sobrevivem. Já &lt;code&gt;message&lt;/code&gt; veio de fábrica marcada como não-enumerável, e morre.&lt;/p&gt;

&lt;p&gt;Não é inconsistência da linguagem: é a mesma regra aplicada a duas propriedades que nasceram de jeitos diferentes.&lt;/p&gt;

&lt;p&gt;Agora o log tem conteúdo. Tem código de erro, tem URL. &lt;strong&gt;Parece informativo.&lt;/strong&gt; E não tem a mensagem.&lt;/p&gt;

&lt;p&gt;Isso é pior que o &lt;code&gt;{}&lt;/code&gt; puro. O &lt;code&gt;{}&lt;/code&gt; pelo menos grita que tem algo errado; esse aqui passa na sua frente e você segue em frente.&lt;/p&gt;

&lt;p&gt;E antes que você pense "ah, mas eu uso classe de erro própria":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AppError&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AppError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;saldo insuficiente&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;E_BALANCE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// '{"name":"AppError","code":"E_BALANCE"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mesma história. &lt;code&gt;name&lt;/code&gt; e &lt;code&gt;code&lt;/code&gt; você atribuiu no construtor — sobrevivem. &lt;code&gt;message&lt;/code&gt; veio do &lt;code&gt;super()&lt;/code&gt; — morre. Você fica sabendo que foi um &lt;code&gt;AppError&lt;/code&gt; de código &lt;code&gt;E_BALANCE&lt;/code&gt;, e não fica sabendo que o saldo era insuficiente.&lt;/p&gt;

&lt;p&gt;Ou seja: praticamente todo erro de domínio que a gente escreve tem esse furo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Spread e Object.assign não salvam
&lt;/h3&gt;

&lt;p&gt;As duas primeiras coisas que a gente tenta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;              &lt;span class="c1"&gt;// '{"code":"E_TIMEOUT"}'&lt;/span&gt;
&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;  &lt;span class="c1"&gt;// '{"code":"E_TIMEOUT"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Os dois copiam só propriedades enumeráveis. Mesmo problema, mais teclas apertadas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Se você usa &lt;code&gt;Error.cause&lt;/code&gt;, ele vai junto
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timeout of 30000ms exceeded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;falha ao processar pedido&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inner&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// '{}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Você encadeou os erros certinho pra não perder o contexto. A cadeia inteira evapora.&lt;/p&gt;

&lt;h2&gt;
  
  
  Como resolver
&lt;/h2&gt;

&lt;p&gt;Serializando o erro na mão. A versão mínima:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serializeError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aquele &lt;code&gt;String(err)&lt;/code&gt; no final não é paranoia. Em JavaScript &lt;strong&gt;qualquer coisa pode ser lançada&lt;/strong&gt; — uma string, um objeto de erro que veio da API de terceiro, um &lt;code&gt;undefined&lt;/code&gt;. Se você assumir que sempre é &lt;code&gt;Error&lt;/code&gt;, o dia em que não for você perde o log exatamente no incidente mais estranho. Que é justamente quando você mais precisava dele.&lt;/p&gt;

&lt;p&gt;A versão que eu levaria pra produção também guarda as propriedades extras e a cadeia de causas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serializeError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// props que a lib pendurou (code, status, config...) — essas são enumeráveis&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cause&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;serializeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora sim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timeout of 30000ms exceeded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;falha ao processar pedido&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inner&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;E_PEDIDO&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;serializeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// {&lt;/span&gt;
&lt;span class="c1"&gt;//   name: "Error",&lt;/span&gt;
&lt;span class="c1"&gt;//   message: "falha ao processar pedido",&lt;/span&gt;
&lt;span class="c1"&gt;//   stack: "Error: falha ao processar pedido\n    at ...",&lt;/span&gt;
&lt;span class="c1"&gt;//   code: "E_PEDIDO",&lt;/span&gt;
&lt;span class="c1"&gt;//   cause: { name: "Error", message: "timeout of 30000ms exceeded", stack: "..." }&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se você não confia em lembrar de chamar isso em todo log — eu não confio —, dá pra plugar globalmente com um &lt;code&gt;replacer&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;replacer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;serializeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;falhou&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;replacer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// '{"msg":"falhou","error":{"name":"Error","message":"boom","stack":"..."}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dá também pra definir um &lt;code&gt;toJSON&lt;/code&gt; no seu erro de domínio, e aí o &lt;code&gt;JSON.stringify&lt;/code&gt; passa a se comportar sozinho:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;toJSON&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AppError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;agora vai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// '{"name":"AppError","message":"agora vai","stack":"..."}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Só que isso cobre &lt;strong&gt;os erros que você escreveu&lt;/strong&gt;. O &lt;code&gt;TypeError&lt;/code&gt; que o runtime lança e o erro que vem do &lt;code&gt;axios&lt;/code&gt; continuam mudos. É complemento, não solução.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;E se você usa &lt;code&gt;pino&lt;/code&gt; ou &lt;code&gt;winston&lt;/code&gt;:&lt;/strong&gt; essas libs já vêm com serializer de erro (&lt;code&gt;pino.stdSerializers.err&lt;/code&gt;, por exemplo). Vale conferir se está ativo e &lt;strong&gt;pra qual chave&lt;/strong&gt; — costuma valer pra &lt;code&gt;err&lt;/code&gt;, e um erro que você logou em &lt;code&gt;error&lt;/code&gt; pode passar batido. É uma linha de config que resolve tudo isso. Se for o seu caso, você acabou de economizar uma tarde.&lt;/p&gt;




&lt;p&gt;Beleza. Log consertado, mensagem aparecendo. Agora vem a parte que eu não esperava.&lt;/p&gt;

&lt;h2&gt;
  
  
  O retry que nunca aconteceu
&lt;/h2&gt;

&lt;p&gt;Com o erro finalmente visível, o alerta seguinte veio assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;timeout of 30000ms exceeded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ótimo — agora dá pra investigar. Fui nos traces reconstruir a timeline, e o desenho que apareceu foi este (os tempos são ilustrativos; o que importa é o formato):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;12:00:00.000   início da execução
12:00:00.050   chama a API externa  →  tentativa 1 do retry
12:00:30.000   timeout of 30000ms exceeded            ← exatos 30s depois
12:00:30.001   "integration failed"                    ← 1 ms depois
12:00:30.080   "erro na chamada, tentativa 1/3"        ← ...opa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Olha a última linha. &lt;strong&gt;O retry loga que a tentativa 1 falhou depois que o chamador já tinha desistido.&lt;/strong&gt; Ele estava anunciando uma segunda tentativa que nunca ia acontecer.&lt;/p&gt;

&lt;p&gt;A causa é a relação entre dois timeouts em saltos encadeados:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;serviço A  ──30s──▶  serviço B  ──30s──▶  API externa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Os dois saltos com o mesmo timeout. E aqui está a pegadinha: &lt;strong&gt;os dois relógios começam juntos.&lt;/strong&gt; Quando a API externa trava, a tentativa 1 estoura no limite — no mesmo instante em que o chamador lá de cima também estoura e derruba a conexão.&lt;/p&gt;

&lt;p&gt;O retry tinha &lt;code&gt;maxRetries: 3&lt;/code&gt; configurado, bonitinho, no lugar certo. E era decorativo. Não importava o número ali: a primeira tentativa consumia &lt;strong&gt;todo&lt;/strong&gt; o orçamento de tempo do chamador. As tentativas 2 e 3 eram inalcançáveis por construção.&lt;/p&gt;

&lt;p&gt;Esse é o tipo de bug que envelhece bem. Não aparece em dashboard nenhum: tem retry configurado, tem alerta disparando, tudo parece saudável. Só que o alerta chegava vazio — e ninguém tinha o menor motivo pra desconfiar do retry.&lt;/p&gt;

&lt;p&gt;Se você guardar uma linha só deste texto, guarde esta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tentativas × timeout_por_tentativa  +  delays  &amp;lt;  timeout de quem te chama
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se essa desigualdade não vale na sua stack, seu retry é enfeite.&lt;/p&gt;

&lt;h2&gt;
  
  
  E não, a correção não é aumentar o timeout
&lt;/h2&gt;

&lt;p&gt;Foi a primeira coisa que me passou pela cabeça: dá mais tempo pro chamador, aí o retry cabe. Antes de mexer, fui olhar a distribuição real da chamada — coisa que qualquer APM entrega em dois cliques.&lt;/p&gt;

&lt;p&gt;O padrão que costuma aparecer em integração com API externa é este:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Métrica&lt;/th&gt;
&lt;th&gt;Tempo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;p50&lt;/td&gt;
&lt;td&gt;~2s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;p95&lt;/td&gt;
&lt;td&gt;~3s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;p99&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~5s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;max&lt;/td&gt;
&lt;td&gt;= o próprio timeout&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Repare no formato, não nos números. 99% das chamadas legítimas terminam em poucos segundos. E aí tem um &lt;strong&gt;abismo&lt;/strong&gt; entre o p99 e o máximo — não tem &lt;em&gt;nada&lt;/em&gt; no meio do caminho. Aquele max não é uma chamada lenta: é uma chamada travada, cortada no teto.&lt;/p&gt;

&lt;p&gt;Quando a distribuição tem essa cara, ela já respondeu a pergunta: &lt;strong&gt;ou a API responde rápido, ou ela trava de vez.&lt;/strong&gt; Não existe meio-termo.&lt;/p&gt;

&lt;p&gt;E isso vira "aumentar o timeout" de cabeça pra baixo. Esperar mais não transforma falha em sucesso — só segura recurso por mais tempo numa requisição que já morreu. É literalmente o oposto do que um timeout existe pra fazer.&lt;/p&gt;

&lt;p&gt;A correção é o contrário do instinto: &lt;strong&gt;encolher o orçamento de cada tentativa&lt;/strong&gt; pra que todas caibam no teto que já existe. Com um chamador de 30s:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;timeout por tentativa:   8s      (~1,6× o p99 — folga sem exagero)
maxRetries:              3
delay entre tentativas:  2s

total = 3 × 8 + 2 × 2 = 28s   &amp;lt;   30s do chamador  ✓
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora o retry funciona de verdade: tentativa 1 falha rápido, espera, tentativa 2 responde nos 2s de sempre (99% de chance), fluxo segue.&lt;/p&gt;

&lt;p&gt;E o melhor: nenhum timeout novo precisa ser criado. O default do framework continua lá, intacto. Só a chamada externa passa um timeout curto, sobrescrevendo pra ela mesma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// em vez de mexer no timeout global do HttpModule&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ATTEMPT_TIMEOUT_MS&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  O que eu tirei disso
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Log ruim não é dívida cosmética.&lt;/strong&gt; É multiplicador do tempo de diagnóstico de todo incidente futuro naquela região do código. Um &lt;code&gt;Error: {}&lt;/code&gt; pode segurar um bug estrutural por meses — e o custo não é o bug, é ninguém conseguir enxergar o bug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retry só é retry se o orçamento couber.&lt;/strong&gt; Se o total do retry não é menor que o timeout de quem te chama, aquele &lt;code&gt;maxRetries: 3&lt;/code&gt; é enfeite de código. Dá pra conferir em cinco minutos, e a falha é silenciosa — ninguém vai te avisar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distribuição decide timeout, não intuição.&lt;/strong&gt; Se o p99 e o max estão a duas ordens de grandeza de distância, você não tem chamada lenta: tem chamada travada. São problemas diferentes, e confundir os dois faz você aumentar exatamente o número que devia diminuir.&lt;/p&gt;




&lt;h3&gt;
  
  
  Cinco minutos pra checar na sua stack
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;grep&lt;/code&gt; por &lt;code&gt;JSON.stringify&lt;/code&gt; no código de log e de alerta. Todo objeto com erro dentro está mudo.&lt;/li&gt;
&lt;li&gt;Confere se o serializer de erro do seu logger está ativo — e pra qual chave.&lt;/li&gt;
&lt;li&gt;Soma o orçamento do seu retry (&lt;code&gt;tentativas × timeout + delays&lt;/code&gt;) e compara com o timeout de quem chama.&lt;/li&gt;
&lt;li&gt;Olha o p99 e o max da sua chamada externa mais crítica. Muito distantes? Seu timeout provavelmente está calibrado pro caso errado.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Se você achar alguma dessas aí, me conta nos comentários. Tenho quase certeza de que não sou o único.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Todo o código deste artigo foi rodado e verificado em Node 20. Os tempos dos exemplos são ilustrativos: o que importa é o formato da distribuição e a relação entre os timeouts, não os números em si.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Alexandre Guimarães — desenvolvedor back-end (Node.js · TypeScript · AWS)&lt;br&gt;
&lt;a href="https://github.com/xandguima" rel="noopener noreferrer"&gt;github.com/xandguima&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/alexandreguimaraes-dev" rel="noopener noreferrer"&gt;linkedin.com/in/alexandreguimaraes-dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>javascript</category>
      <category>typescript</category>
      <category>ptbr</category>
    </item>
  </channel>
</rss>
