可怕!我的Nodejs系统因为日志打印了Error 对象就崩溃了 Node.js System Crashed Because of Logging an Error

news/2025/10/14 1:33:41/文章来源:https://www.cnblogs.com/sugartang/p/19139840

@

目录
    • 报错信息
    • 报错截图
    • 错误分析
    • 场景复现
    • 小秘密大揭秘!🔍
      • console.log虽好,但请勿用它来记录PROD错误!
      • 日志库的"小烦恼"
    • 什么是循环引用?🌀
    • 怎样才能让我们的日志系统乖乖听话呢?✨
      • 1. 只记录我们需要的信息
      • 2. 使用专门的错误序列化函数
      • 3. 使用成熟的错误处理库
      • 4. 配置日志库的防护机制
    • 最佳实践小贴士 🌟
  • Terrifying! My Node.js System Crashed Because of Logging an Error Object 😱
    • Error Message
    • Error Screenshot
    • Error Analysis
    • Reproducing the Scenario
    • The Big Reveal of Little Secrets! 🔍
      • console.log is Good, But Don't Use It to Log PROD Errors!
      • The "Little Troubles" of Logging Libraries
    • What Are Circular References? 🌀
    • How to Make Our Logging System Behave? ✨
      • 1. Only Log the Information We Need
      • 2. Use a Dedicated Error Serialization Function
      • 3. Use Mature Error Handling Libraries
      • 4. Configure Protective Mechanisms for Logging Libraries
    • Best Practice Tips 🌟
    • Conclusion | 结语

本文为中英文双语,需要英文博客可以滑动到下面查看哦 | This is a bilingual article. Scroll down for the English version.

小伙伴们!今天我在本地调试项目的过程中,想记录一下错误信息,结果程序就"啪"地一下报出 "Maximum call stack size exceeded" 错误,然后项目直接就crash了。但是我看我用的这个开源项目,官方的代码里好多地方就是这么用的呀?我很纳闷,这是为什么呢?

Snipaste_2025-10-10_00-28-45

报错信息


[LOGGER PARSING ERROR] Maximum call stack size exceeded
2025-10-13T17:06:59.643Z debug: Error code: 400 - {'error': {'message': 'Budget has been exceeded! Current cost: 28.097367900000002, Max budget: 0.0', 'type': 'budget_exceeded', 'par... [truncated]
{unknown: [object Object],
}
2025-10-13T17:06:59.643Z debug: [api/server/middleware/abortMiddleware.js] respondWithError called
2025-10-13T17:06:59.644Z error: There was an uncaught error: Cannot read properties of undefined (reading 'emit')
2025-10-13T17:06:59.645Z debug: [indexSync] Clearing sync timeouts before exiting...
[nodemon] app crashed - waiting for file changes before starting...

报错截图

image

错误分析

晚上下班以后,晚上躺在床上,我翻来覆去睡不着,干脆打开电脑一番探究,想要知道 ,这个错误到底为何触发,实质原因是什么,以及如何解决它。让我们一起把这个小调皮鬼揪出来看看它到底在搞什么鬼吧!👻

场景复现

想象一下这个场景,你正在开心地写着代码:

app.get('/api/data', async (req, res) => {try {// 一些可能会出小差错的业务逻辑const data = await fetchDataFromAPI();res.json(data);} catch (error) {// 记录错误信息logger.debug('获取数据时出错啦~', error); // 哎呀!这一行可能会让我们的程序崩溃哦!res.status(500).json({ error: '内部服务器出错啦~' });}
});

看起来是不是很正常呢?但是当你运行这段代码的时候,突然就出现了这样的错误:

[LOGGER PARSING ERROR] Maximum call stack size exceeded

更神奇的是,如果你把代码改成这样:

console.log(error); // 这一行却不会让程序崩溃哦,但是上prod的系统,不要这么用哦

它就能正常工作啦!这是为什么呢?🤔

小秘密大揭秘!🔍

console.log虽好,但请勿用它来记录PROD错误!

console.log 是 Node.js 原生提供的函数,它就像一个经验超级丰富的大叔,知道怎么处理各种"调皮"的对象。当 console.log 遇到包含循环引用的对象时,它会聪明地检测这些循环引用,并用 [Circular] 标记来代替实际的循环部分,这样就不会无限递归啦!

简单来说,Node.js 的 console.log 就像一个超级厉害的武林高手,知道如何闪转腾挪,避开各种陷阱!🥋

日志库的"小烦恼"

但是我们自己封装的日志系统(比如项目中使用的 Winston)就不一样啦!为了实现各种炫酷的功能(比如格式化、过滤敏感信息等),日志库通常会使用一些第三方库来处理传入的对象。

在我们的案例中,日志系统使用了 [traverse] 库来遍历对象。这个库在大多数情况下工作得都很好,但当它遇到某些复杂的 Error 对象时,就可能会迷路啦!

Error 对象可不是普通对象那么简单哦!它们可能包含各种隐藏的属性、getter 方法,甚至在某些情况下会动态生成属性。当 [traverse] 库尝试遍历这些复杂结构时,就可能陷入无限递归的迷宫,最终导致调用栈溢出。

什么是循环引用?🌀

在深入了解这个问题之前,我们先来了解一下什么是循环引用。循环引用指的是对象之间相互引用,形成一个闭环。比如说:

const objA = { name: '小A' };
const objB = { name: '小B' };objA.ref = objB;
objB.ref = objA; // 哎呀!形成循环引用啦!

当尝试序列化这样的对象时(比如用 JSON.stringify),就会出现问题,因为序列化过程会无限递归下去,就像两只小仓鼠在滚轮里永远跑不完一样!🐹

Error 对象虽然看起来简单,但内部结构可能非常复杂,特别是在一些框架或库中创建的 Error 对象,它们可能包含对 request、response 等对象的引用,而这些对象又可能包含对 Error 对象的引用,从而形成复杂的循环引用网络,就像一张大蜘蛛网一样!🕷️

怎样才能让我们的日志系统乖乖听话呢?✨

1. 只记录我们需要的信息

最简单直接的方法就是不要把整个 Error 对象传递给日志函数,而是只传递我们需要的具体属性:

// ❌ 不推荐的做法 - 会让日志系统"生气"
logger.debug('获取数据时出错啦~', error);// ✅ 推荐的做法 - 让日志系统开心地工作
logger.debug('获取数据时出错啦~', {message: error.message,stack: error.stack,code: error.code
});

2. 使用专门的错误序列化函数

你可以创建一个专门用于序列化 Error 对象的函数,就像给 Error 对象穿上一件"安全外套":

function serializeError(error) {return {name: error.name,message: error.message,stack: error.stack,code: error.code,// 添加其他你需要的属性};
}// 使用方式
logger.debug('获取数据时出错啦~', serializeError(error));

3. 使用成熟的错误处理库

有些库专门为处理这类问题而设计,比如 serialize-error,它们就像专业的保姆一样,会把 Error 对象照顾得好好的:

const { serializeError } = require('serialize-error');logger.debug('获取数据时出错啦~', serializeError(error));

4. 配置日志库的防护机制

如果你使用的是 Winston,可以配置一些防护机制,给它穿上"防弹衣":

const winston = require('winston');const logger = winston.createLogger({format: winston.format.combine(winston.format.errors({ stack: true }),winston.format.json()),// ... 其他配置
});

最佳实践小贴士 🌟

  1. 永远不要直接记录原始的 Error 对象:它们可能包含复杂的循环引用结构,就像一个调皮的小恶魔。

  2. 提取关键信息:只记录我们需要的错误信息,比如 message、stack 等,就像挑选糖果一样只拿最喜欢的。

  3. 使用安全的序列化方法:确保我们的日志系统能够处理各种边界情况,做一个贴心的小棉袄。

  4. 添加防护措施:在日志处理逻辑中添加 try-catch 块,防止日志系统本身成为故障点,就像给程序戴上安全帽。

  5. 测试边界情况:在测试中模拟各种错误场景,确保日志系统在极端情况下也能正常工作,做一个负责任的好孩子。

image

Terrifying! My Node.js System Crashed Because of Logging an Error Object 😱

Fellow developers! Today, while debugging a project locally, I wanted to log some error information, but suddenly the program threw a "Maximum call stack size exceeded" error and crashed the entire project. But when I look at the open-source project I'm using, I see that the official code does this in many places. I was puzzled, why is this happening?

Error Message


[LOGGER PARSING ERROR] Maximum call stack size exceeded
2025-10-13T17:06:59.643Z debug: Error code: 400 - {'error': {'message': 'Budget has been exceeded! Current cost: 28.097367900000002, Max budget: 0.0', 'type': 'budget_exceeded', 'par... [truncated]
{unknown: [object Object],
}
2025-10-13T17:06:59.643Z debug: [api/server/middleware/abortMiddleware.js] respondWithError called
2025-10-13T17:06:59.644Z error: There was an uncaught error: Cannot read properties of undefined (reading 'emit')
2025-10-13T17:06:59.645Z debug: [indexSync] Clearing sync timeouts before exiting...
[nodemon] app crashed - waiting for file changes before starting...

Error Screenshot

image

Error Analysis

After work, I couldn't resist investigating why this error was triggered, what the root cause was, and how to solve it. Let's together catch this little troublemaker and see what it's up to! 👻

Reproducing the Scenario

Imagine this scenario, you're happily coding:

app.get('/api/data', async (req, res) => {try {// Some business logic that might go wrongconst data = await fetchDataFromAPI();res.json(data);} catch (error) {// Log the errorlogger.debug('Error fetching data~', error); // Oops! This line might crash our program!res.status(500).json({ error: 'Internal server error~' });}
});

Doesn't this look normal? But when you run this code, suddenly this error appears:

[LOGGER PARSING ERROR] Maximum call stack size exceeded

What's even more神奇 is, if you change the code to this:

console.log(error); // This line won't crash the program, but don't use this in production systems

It works fine! Why is that? 🤔

The Big Reveal of Little Secrets! 🔍

console.log is Good, But Don't Use It to Log PROD Errors!

console.log is a native Node.js function. It's like an extremely experienced uncle who knows how to handle all kinds of "naughty" objects. When console.log encounters objects with circular references, it cleverly detects these circular references and replaces the actual circular parts with [Circular] markers, so it won't recurse infinitely!

Simply put, Node.js's console.log is like a super skilled martial arts master who knows how to dodge and avoid all kinds of traps! 🥋

The "Little Troubles" of Logging Libraries

But our custom logging systems (like Winston used in the project) are different! To implement various cool features (like formatting, filtering sensitive information, etc.), logging libraries often use third-party libraries to process incoming objects.

In our case, the logging system uses the [traverse] library to traverse objects. This library works well in most cases, but when it encounters certain complex Error objects, it might get lost!

Error objects are not as simple as ordinary objects! They may contain various hidden properties, getter methods, and in some cases, dynamically generated properties. When the [traverse] library tries to traverse these complex structures, it may fall into an infinite recursion maze, ultimately causing a stack overflow.

What Are Circular References? 🌀

Before diving deeper into this issue, let's first understand what circular references are. Circular references refer to objects that reference each other, forming a closed loop. For example:

const objA = { name: 'A' };
const objB = { name: 'B' };objA.ref = objB;
objB.ref = objA; // Oops! Circular reference formed!

When trying to serialize such objects (like with JSON.stringify), problems arise because the serialization process will recurse infinitely, like two hamsters running forever in a wheel! 🐹

Although Error objects look simple, their internal structure can be very complex, especially Error objects created in some frameworks or libraries. They may contain references to request, response, and other objects, and these objects may in turn contain references to the Error object, forming a complex circular reference network, like a giant spider web! 🕷️

How to Make Our Logging System Behave? ✨

1. Only Log the Information We Need

The simplest and most direct method is not to pass the entire Error object to the logging function, but to pass only the specific properties we need:

// ❌ Not recommended - will make the logging system "angry"
logger.debug('Error fetching data~', error);// ✅ Recommended - makes the logging system work happily
logger.debug('Error fetching data~', {message: error.message,stack: error.stack,code: error.code
});

2. Use a Dedicated Error Serialization Function

You can create a dedicated function for serializing Error objects, like putting a "safety coat" on the Error object:

function serializeError(error) {return {name: error.name,message: error.message,stack: error.stack,code: error.code,// Add other properties you need};
}// Usage
logger.debug('Error fetching data~', serializeError(error));

3. Use Mature Error Handling Libraries

Some libraries are specifically designed to handle these kinds of issues, such as serialize-error. They're like professional nannies who will take good care of Error objects:

const { serializeError } = require('serialize-error');logger.debug('Error fetching data~', serializeError(error));

4. Configure Protective Mechanisms for Logging Libraries

If you're using Winston, you can configure some protective mechanisms to give it "bulletproof armor":

const winston = require('winston');const logger = winston.createLogger({format: winston.format.combine(winston.format.errors({ stack: true }),winston.format.json()),// ... other configurations
});

Best Practice Tips 🌟

  1. Never log raw Error objects directly: They may contain complex circular reference structures, like a mischievous little devil.

  2. Extract key information: Only log the error information we need, such as message, stack, etc., like picking candy - only take your favorites.

  3. Use safe serialization methods: Ensure our logging system can handle various edge cases, be a thoughtful companion.

  4. Add protective measures: Add try-catch blocks in the logging logic to prevent the logging system itself from becoming a failure point, like giving the program a safety helmet.

  5. Test edge cases: Simulate various error scenarios in testing to ensure the logging system works properly under extreme conditions, be a responsible good child.

Conclusion | 结语

  • That's all for today~ - | 今天就写到这里啦~

  • Guys, ( ̄ω ̄( ̄ω ̄〃 ( ̄ω ̄〃)ゝ See you tomorrow~ | 小伙伴们,( ̄ω ̄( ̄ω ̄〃 ( ̄ω ̄〃)ゝ我们明天再见啦~~

  • Everyone, be happy every day! 大家要天天开心哦

  • Welcome everyone to point out any mistakes in the article~ | 欢迎大家指出文章需要改正之处~

  • Learning has no end; win-win cooperation | 学无止境,合作共赢

  • Welcome all the passers-by, boys and girls, to offer better suggestions! ~ | 欢迎路过的小哥哥小姐姐们提出更好的意见哇~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/936557.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实践

ans多取合法方案的max/min结果肯定不劣。对于操作“change x y:把a[x]修改为y”,无论是提前continue掉还是循环末尾,一定要记得令a[x]=y!!!模数MOD特殊一定要注意!遇见小模数MOD,可能复杂度与MOD相关。 有可能…

数据结构字符串和图

1.字符串的存储 1.1.字符数组和STLstring char s[N]strlen(s+i):\(O(n)\)。返回从 s[0+i] 开始直到 \0 的字符数。 strcmp(s1,s2):\(O(\min(n_1,n_2))\)。若 s1 字典序更小返回负值,两者一样返回 0,s1 字典序更大返…

字典dict

2025.10.14 1.字典的键值必须是不可变的,也就是说元祖,形如下面的初始化是可以的dict1 = {(1, 2): 1} dict1 = {a: 1} dict1 = {}

结婚证识别技术:融合计算机视觉、深度学习与自然语言处理的综合性AI能力的体现

在数字化浪潮席卷各行各业的今天,如何高效、准确地处理海量纸质证件信息,成为提升政务服务与金融业务效率的关键。结婚证作为证明婚姻关系的核心法律文件,因而,结婚证识别技术应运而生。它不仅是光学字符识别技术的…

上下文丢失

2025.10.14 位置编码外推失效是Transformer模型在长文本推理中出现上下文丢失的最常见架构限制,因为训练时使用的固定位置编码(如正弦编码)无法有效外推至超出训练长度的序列位置,导致位置信息丢失。 残差连接梯度…

数据结构序列

不要从数据结构维护信息的角度来思考问题,而是从问题本身思考需要哪些信息,数据结构只是维护信息的工具!!! 可减信息,如区间和、区间异或和 直接用前缀和实现,复杂度 O(n)+O(1)+O(n)。 可重复贡献信息,如区间最…

上下文学习(In-context Learning, ICL)

2025.10.14 上下文学习(In-context Learning, ICL)的核心机制是在推理阶段不更新模型参数,利用提示中的少量示例引导模型生成适应新任务的输出。也就是在不更新参数的情况下,利用提示中的示例让模型在内部条件化地…

混淆矩阵

2025.10.14 混淆矩阵可以显示模型的所有预测结果,包括真正例、假正例、真负例和假负例,从而帮助分析模型的性能 混淆矩阵不仅仅显示准确率,还提供更详细的分类结果 混淆矩阵与训练损失无关 混淆矩阵不涉及超参数设置…

提示词工程实践指南:从调参到对话的范式转变

写在前面 作为一名长期与代码打交道的工程师,我们习惯了编译器的严格和确定性——相同的输入永远产生相同的输出。但当我们开始使用生成式AI时,会发现这是一个完全不同的世界。最近在系统学习Google的AI课程时,我整理…

泛化能力

2025.10.14 在大型语言模型的工程实践中,提高泛化能力的最常见策略是使用更大的预训练数据集,因为更多数据可以帮助模型学习更泛化的表示,例如GPT-3和BERT等模型都强调大规模数据集的应用。

JVM引入

虚拟机与 JVM 虚拟机(Virtual Machine),就是一台虚拟的计算机。它是一款软件,用来执行一系列的虚拟计算机指令。 虚拟机可以分为系统虚拟机和程序虚拟机:Visual Box、VMware 就属于系统虚拟机,它们完全是对物理计…

shiro 架构

一、subject(当前用户信息) 二、SecurityManager(所有用户管理) 三、Realm(数据连接)

[音视频][HLS] HLS_downloader

[音视频][HLS] HLS_downloader$(".postTitle2").removeClass("postTitle2").addClass("singleposttitle");01 简介 1.1 功能: 一个简单的HLS下载器,使用go语言实现 1.2 执行方式 如果…

Python-weakref技术指南

Python weakref 模块是 Python 标准库中用于处理对象弱引用的重要工具。它允许程序员创建对对象的弱引用,这种引用不会增加对象的引用计数,从而不影响对象的垃圾回收过程。本报告将全面介绍 weakref 模块的概念、工作…

从众多知识汲取一星半点也能受益匪浅【day11(2025.10.13)】

Enjoy 基于代码思考问题 先理清楚代码是否用上了文档所定义的api

王爽《汇编语言》第四章 笔记

4.2 源程序 4.2.1 伪指令在汇编语言的源程序中包含两种指令:汇编指令、伪指令。 (1)汇编指令:有对应机器码的指令,可以被编译为机器指令,最终被CPU所执行。 (2)伪指令:没有对应的机器指令,最终不被CPU所执行…

10.13总结

import java.util.*; import java.util.concurrent.TimeUnit; public class ArithmeticPractice { private Set generatedQuestions = new HashSet<>(); private List questions = new ArrayList<>(); pri…

MySql安装中的问题

是一台已经安装过但是失败了的win 1. 2025-10-13T12:42:20.566779Z 0 [ERROR] [MY-010457] [Server] --initialize specified but the data directory has files in it. Aborting. 2025-10-13T12:42:20.566788Z 0 [ERR…

10.14总结

import java.util.*; import java.util.concurrent.TimeUnit; public class ArithmeticPractice { private Set generatedQuestions = new HashSet<>(); private List questions = new ArrayList<>(); pri…