引言
在前端开发中,异常监控是保障应用稳定性的关键一环。当用户遇到页面白屏、功能不可用等问题时,如果能及时收集到详细的错误信息(包括堆栈、行列号、浏览器环境等),就能快速定位并修复 bug。浏览器提供了 window.onerror 和 unhandledrejection 两个全局事件,分别用于捕获未处理的 JavaScript 异常和未捕获的 Promise 拒绝。
然而,不同浏览器对这些事件的参数支持存在差异,错误对象的格式也各不相同。如何编写一个兼容所有浏览器、并能像 console.log(error) 那样输出完整堆栈的格式化函数,是搭建前端监控系统的第一步。
本文将深入理解 console.log(error) 的底层实现,给出通用的错误格式化方案,并演示如何将格式化后的异常信息上报到后端。
为什么需要统一格式化?
当你在控制台直接执行 console.log(new Error('something wrong')) 时,浏览器会打印出类似这样的信息:
Error: something wrong at <anonymous>:1:13 at ...
但如果使用 window.onerror 捕获,你拿到的参数可能只有消息、脚本 URL、行号、列号和一个可选的 error 对象。这些参数组合起来未必能还原出完整的堆栈。此外,unhandledrejection 的 reason 可能是任意类型(字符串、对象、Error 实例等),如何安全地提取信息并拼接成可读的字符串,也需要仔细处理。
一个优秀的异常上报方案应该做到:
- 完整性:尽可能包含错误名称、消息、调用堆栈、发生位置(文件、行号、列号)。
- 兼容性:支持所有主流浏览器(包括 IE9+)。
- 健壮性:处理循环引用、非 Error 对象等特殊情况,避免二次异常。
- 一致性:最终上报的字符串格式统一,便于后端解析或搜索。
console.log(error) 的底层原理
在深入实现之前,我们先了解一下浏览器是如何打印错误对象的。以 Chrome 的 V8 引擎为例:
console.log接收一个对象后,会调用该对象的[Symbol.toStringTag]或自定义的inspect方法(DevTools 扩展)。对于 Error 对象,V8 内部会检查其是否有stack属性。error.stack是一个非标准但所有现代浏览器都支持的属性,它包含了当前调用栈的快照。这个堆栈字符串的生成依赖于Error.captureStackTrace(Node.js 中)或运行时自动收集的调用帧。- 如果
error.stack存在,浏览器直接输出该字符串;否则,退而使用error.toString()(通常是"Error: message"的形式)。
因此,要获得与 console.log 相同的输出,我们只需在全局事件中尽量获取到 error.stack 即可。当无法获取 stack 时,再根据事件参数手动拼接位置信息。
统一错误格式化函数
下面是一个健壮的 formatError 函数,它接受任意类型的错误值以及可选的 URL、行号、列号,返回格式化的错误字符串。
/**
* 将任意错误值格式化为包含堆栈信息的字符串
* @param {*} - 错误对象或任意值
* {} - 当无法获取有效信息时的备选消息
* {} [url] - 发生错误的脚本 URL(从 onerror 获取)
* {} [line] - 行号(从 onerror 获取)
* {} [col] - 列号(从 onerror 获取)
* {} 格式化后的错误字符串
*/
() {
result = ;
(error && error === ) {
( error. === ) {
result = error.;
}
( error. === ) {
name = error. || ;
result = ;
}
{
{
result = .(error, , );
} (e) {
result = (error);
}
}
} {
result = (error);
}
hasLineInfo = .(result);
(!hasLineInfo && url && line) {
location = ;
result = result ? : ;
}
(!result && fallbackMessage) {
result = fallbackMessage;
}
result;
}


