前端异常捕获与统一格式化:从 console.log(error) 到服务端上报

前端异常捕获与统一格式化:从 console.log(error) 到服务端上报
🧑 博主简介ZEEKLOG博客专家「历代文学网」(公益文学网,PC端可以访问:https://lidaiwenxue.com/#/?__c=1000,移动端可关注公众号 “ 心海云图 ” 微信小程序搜索“历代文学”)总架构师,首席架构师,也是联合创始人!16年工作经验,精通Java编程高并发设计分布式系统架构设计Springboot和微服务,熟悉LinuxESXI虚拟化以及云原生Docker和K8s,热衷于探索科技的边界,并将理论知识转化为实际应用。保持对新技术的好奇心,乐于分享所学,希望通过我的实践经历和见解,启发他人的创新思维。在这里,我希望能与志同道合的朋友交流探讨,共同进步,一起在技术的世界里不断学习成长。
🤝商务合作:请搜索或扫码关注微信公众号 “ 心海云图


在这里插入图片描述

前端异常捕获与统一格式化:从 console.log(error) 到服务端上报

引言

在前端开发中,异常监控是保证应用稳定性的重要一环。当用户遇到页面白屏、功能不可用等问题时,如果能及时收集到详细的错误信息(包括堆栈、行列号、浏览器环境等),就能快速定位并修复 bug。浏览器提供了 window.onerrorunhandledrejection 两个全局事件,分别用于捕获未处理的 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 对象。这些参数组合起来未必能还原出完整的堆栈。此外,unhandledrejectionreason 可能是任意类型(字符串、对象、Error 实例等),如何安全地提取信息并拼接成可读的字符串,也需要仔细处理。

一个优秀的异常上报方案应该做到:

  • 完整性:尽可能包含错误名称、消息、调用堆栈、发生位置(文件、行号、列号)。
  • 兼容性:支持所有主流浏览器(包括 IE9+)。
  • 健壮性:处理循环引用、非 Error 对象等特殊情况,避免二次异常。
  • 一致性:最终上报的字符串格式统一,便于后端解析或搜索。

console.log(error) 的底层原理

在深入实现之前,我们先了解一下浏览器是如何打印错误对象的。以 Chrome 的 V8 引擎为例:

  1. console.log 接收一个对象后,会调用该对象的 [Symbol.toStringTag] 或自定义的 inspect 方法(DevTools 扩展)。对于 Error 对象,V8 内部会检查其是否有 stack 属性。
  2. error.stack 是一个非标准但所有现代浏览器都支持的属性,它包含了当前调用栈的快照。这个堆栈字符串的生成依赖于 Error.captureStackTrace(Node.js 中)或运行时自动收集的调用帧。
  3. 如果 error.stack 存在,浏览器直接输出该字符串;否则,退而使用 error.toString()(通常是 "Error: message" 的形式)。

因此,要获得与 console.log 相同的输出,我们只需在全局事件中尽量获取到 error.stack 即可。当无法获取 stack 时,再根据事件参数手动拼接位置信息。

统一错误格式化函数

下面是一个健壮的 formatError 函数,它接受任意类型的错误值以及可选的 URL、行号、列号,返回格式化的错误字符串。

/** * 将任意错误值格式化为包含堆栈信息的字符串 * @param {*} error - 错误对象或任意值 * @param {string} fallbackMessage - 当无法获取有效信息时的备选消息 * @param {string} [url] - 发生错误的脚本URL(从onerror获取) * @param {number} [line] - 行号(从onerror获取) * @param {number} [col] - 列号(从onerror获取) * @returns {string} 格式化后的错误字符串 */functionformatError(error, fallbackMessage, url, line, col){let result ='';// 情况1:error 是对象类型,尝试提取 stack 或 messageif(error &&typeof error ==='object'){// 优先使用 stack(包含完整的调用堆栈)if(typeof error.stack ==='string'){ result = error.stack;}// 其次使用标准 error 属性(name 和 message)elseif(typeof error.message ==='string'){const name = error.name ||'Error'; result =`${name}: ${error.message}`;}// 否则尝试 JSON 序列化(避免循环引用)else{try{ result =JSON.stringify(error,null,2);}catch(e){// 序列化失败(如循环引用),使用默认字符串转换 result =String(error);}}}else{// 原始类型直接转为字符串 result =String(error);}// 情况2:结果中不包含行列信息(如只拿到 message),但通过 onerror 获得了具体位置// 简单判断堆栈中是否已有类似 ":数字" 的行号标记const hasLineInfo =/:\d+/.test(result);if(!hasLineInfo && url && line){const location =`${url}:${line}${col ?':'+ col :''}`; result = result ?`${result} at ${location}`:`Error at ${location}`;}// 情况3:仍然没有有效内容,使用 fallbackMessageif(!result && fallbackMessage){ result = fallbackMessage;}return result;}

关键点说明

  • 优先使用 error.stack:只要错误对象有 stack 属性,就直接使用它,因为 stack 已经包含了最完整的调用链和位置信息。
  • 降级使用 namemessage:如果对象是 Error 实例但 stack 可能被篡改或不存在,则拼接 name: message
  • JSON 序列化兜底:对于普通对象(如 { code: 500, msg: 'fail' }),尝试用 JSON.stringify 展示其结构,并捕获循环引用异常。
  • 附加行列号:当最终字符串中没有明显的数字位置(如 :10)且外部提供了 URL 和行号时,将位置信息附加到末尾。这可以弥补某些场景下 error.stack 缺失行列的不足。
  • fallbackMessage 参数:当 error 为 undefined 或空值时,可以传入默认消息,例如 'Unhandled Rejection'

全局监听器:window.onerror 和 unhandledrejection

有了格式化函数,我们就可以在全局事件中调用它,并将结果上报。

window.onerror

window.onerror=function(message, source, lineno, colno, error){const errorStr =formatError(error, message, source, lineno, colno);// 上报错误(示例:使用 sendToServer 函数)sendToServer({type:'onerror',message: message,stack: errorStr,url: source,line: lineno,column: colno,userAgent: navigator.userAgent,timestamp: Date.now()});// 返回 true 可以阻止浏览器默认处理(如控制台打印错误)// return true;};

注意:旧版 IE(<=10)不会传递 error 参数,此时 errorundefined,我们的 formatError 会使用 fallbackMessage(即 message)和行列号来构造字符串。

unhandledrejection

window.addEventListener('unhandledrejection',function(event){const reason = event.reason;const errorStr =formatError(reason,'Unhandled Rejection');sendToServer({type:'unhandledrejection',reason: errorStr,userAgent: navigator.userAgent,timestamp: Date.now()});// 可选:阻止默认行为(某些浏览器会打印错误) event.preventDefault();});

event.reason 可以是任何类型,我们的 formatError 已经做了充分处理。

上报函数实现

最简单的上报可以通过 navigator.sendBeaconfetch 发送到后端接口。为了不影响用户体验,建议使用 sendBeacon,它会在页面卸载时也能确保请求发出。

functionsendToServer(data){// 避免频繁上报(例如使用采样率)if(Math.random()>0.1)return;// 10% 采样const url ='https://your-monitor-server.com/api/error';const body =JSON.stringify(data);if(navigator.sendBeacon){ navigator.sendBeacon(url, body);}else{fetch(url,{method:'POST',headers:{'Content-Type':'application/json'},body: body,keepalive:true// 类似 sendBeacon 的行为}).catch(()=>{});// 忽略 fetch 失败}}

兼容性深度解析

不同浏览器的 window.onerror 参数

浏览器messagesourcelinenocolnoerror
Chrome / Firefox / Safari / Edge (现代)✔️✔️✔️✔️✔️
IE 10+✔️✔️✔️✔️✔️(但可能为 null)
IE 9-✔️✔️✔️

我们的 formatError 能够适应以上所有情况:当 error 不存在时,利用 messagesourcelineno 构造一个简化版本。

堆栈格式差异

不同浏览器生成的 error.stack 格式略有不同,例如:

  • Chrome: Error: message\n at function (file:line:column)
  • Firefox: Error: message\n function@file:line:column
  • Safari: Error: message\n function@file:line:column
  • IE: Error: message\n at function (file:line:column)

这些格式差异通常不影响可读性,我们的格式化函数直接保留原始 stack,不进行解析和重组,以保证信息不丢失。

完整示例代码

将上述片段整合,得到一个完整的监控模块:

// error-monitor.js(function(){'use strict';functionformatError(error, fallbackMessage, url, line, col){let result ='';if(error &&typeof error ==='object'){if(typeof error.stack ==='string'){ result = error.stack;}elseif(typeof error.message ==='string'){const name = error.name ||'Error'; result =`${name}: ${error.message}`;}else{try{ result =JSON.stringify(error,null,2);}catch(e){ result =String(error);}}}else{ result =String(error);}const hasLineInfo =/:\d+/.test(result);if(!hasLineInfo && url && line){const location =`${url}:${line}${col ?':'+ col :''}`; result = result ?`${result} at ${location}`:`Error at ${location}`;}if(!result && fallbackMessage){ result = fallbackMessage;}return result;}functionsendToServer(data){// 采样:仅上报 10% 的错误,可根据需要调整if(Math.random()>0.1)return;const url ='https://your-monitor-server.com/api/error';const body =JSON.stringify({...data,userAgent: navigator.userAgent,timestamp: Date.now(),page: window.location.href });if(navigator.sendBeacon){ navigator.sendBeacon(url, body);}else{fetch(url,{method:'POST',headers:{'Content-Type':'application/json'},body: body,keepalive:true}).catch(()=>{});}} window.onerror=function(message, source, lineno, colno, error){const errorStr =formatError(error, message, source, lineno, colno);sendToServer({type:'onerror',rawMessage: message,stack: errorStr,url: source,line: lineno,column: colno });}; window.addEventListener('unhandledrejection',function(event){const reason = event.reason;const errorStr =formatError(reason,'Unhandled Rejection');sendToServer({type:'unhandledrejection',stack: errorStr }); event.preventDefault();});})();

进阶考虑

1. 去重与聚合

大量相同错误重复上报会浪费资源。可以在前端缓存最近上报的错误指纹(如 error.stack 的哈希),短时间内相同的错误不再发送。

2. 错误采样

对于高流量的应用,可以设置采样率,只上报一部分错误,减轻服务器压力。

3. 附加上下文

除了错误信息,还可以记录用户的登录状态、操作路径、API 请求参数等,帮助复现问题。

4. 跨域脚本的堆栈

如果引用了 CDN 上的脚本,错误堆栈中可能只有 Script error. 而没有详细信息。需要为脚本添加 crossorigin="anonymous" 属性,并确保服务器响应头包含 Access-Control-Allow-Origin

总结

本文从 console.log(error) 的底层原理出发,设计了一个兼容所有浏览器的错误格式化函数,并结合 window.onerrorunhandledrejection 实现了全局异常捕获与上报。这个方案能够像原生控制台一样输出完整的错误堆栈,同时处理了各种边界情况(非 Error 对象、旧版 IE、循环引用等)。将此模块集成到项目中,你就拥有了一个可靠的前端监控基础,为后续的故障排查和数据分析奠定坚实的基础。

前端异常监控并非一劳永逸,还需要不断优化上报策略、丰富上下文信息,以及结合后端分析工具形成闭环。但至少,从今天开始,你不再对用户的错误一无所知。

Read more

一:ROS2+gazebo+PX4环境搭建:环境搭建到无人机起飞

前言 写博客记录学习的过程。 由于博客是安装完环境后写的,因此实际可能会有一些出入,但是实际上也大差不差的。 环境搭建 前置环境 * ROS2 humble * Gazebo Classic (11) 1.下载 PX4 源码 在你的 Home 目录下,用 Git 克隆 PX4 的代码仓库,并更新所有子模块。 git clone https://github.com/PX4/PX4-Autopilot.git --recursive 注意:由于这个源码中包含许多子模块,因此不建议到github主页下载zip再解压,这样做会缺失许多子模块。使用大陆的网络克隆起来会十分慢,因此强烈建议大家使用网络加速! 2.运行自动安装脚本 PX4 提供了自动化脚本,可以帮你安装编译仿真环境所需的所有依赖。 cd PX4-Autopilot bash ./Tools/setup/

By Ne0inhk

新松GCR系列协作机器人软件操作全解析:从入门到精通

1. 初识新松GCR:你的第一个协作机器人伙伴 大家好,我是老张,在工业自动化和协作机器人这个行当里摸爬滚打了十几年。今天,我想和大家聊聊新松的GCR系列协作机器人。如果你刚拿到一台GCR机器人,面对那个看起来有点复杂的示教器和软件界面,心里有点发怵,别担心,这太正常了。我当年第一次接触时,也花了不少时间才摸清门道。协作机器人和传统的工业机器人不太一样,它更强调“与人共舞”,安全性高、部署灵活,但这也意味着它的软件操作逻辑需要我们重新适应。 新松GCR系列,比如常见的GCR5-910、GCR10-1300这些型号,它们的核心“大脑”就是我今天要重点讲的软件系统。这套系统,你可以把它理解为你和机器人之间沟通的“语言”。它不像传统工业机器人那样,需要你写一大堆复杂的代码,而是通过一个图形化的示教器和一套脚本语言,让你能像搭积木一样,把机器人的动作、逻辑、交互给组合起来。无论是简单的物料搬运、上下料,还是复杂的装配、检测,甚至是需要和视觉系统、PLC打交道的复杂产线,都离不开这套软件的熟练操作。 我见过不少工程师,硬件接线、机械安装都搞得飞快,但一到软件调试环节就卡壳,一个简单的点位示

By Ne0inhk
零代码上手!用 Rokid 灵珠平台,5 步搭建专属旅游 AR 智能体

零代码上手!用 Rokid 灵珠平台,5 步搭建专属旅游 AR 智能体

零代码上手!用 Rokid 灵珠平台,5 步搭建专属旅游 AR 智能体 灵珠平台简介 okid 自研 AI 开发平台,基于多模态大模型与轻量化架构,打造零门槛、全栈化 AI 开发体系。平台提供可视化编排、预置能力组件,支持原型到云端、端侧一站式敏捷部署,并深度适配 Rokid Glasses 智能眼镜,通过专属硬件接口与低功耗优化,实现 AI 应用高效端侧落地,助力开发者快速打造视觉识别、语音交互等穿戴式 AI 应用,拓展 AI + 物理世界的交互边界可视化编排工具,拖拽式快速搭建应用预置丰富能力组件库,涵盖对话引擎、视觉识别等核心模块支持从原型设计到云端、端侧的一站式敏捷部署提供设备专属适配接口,实现硬件深度协同搭载低功耗运行优化方案,保障端侧持久稳定运行 实战:搭建旅游类AR智能体 1、进入灵珠平台 登录灵珠平台后,你将看到简洁直观的工作台界面 点击创建智能体按钮,

By Ne0inhk
LazyLLM 测评 | 低代码颠覆 AI 开发!代码专家智能体进阶模块实战

LazyLLM 测评 | 低代码颠覆 AI 开发!代码专家智能体进阶模块实战

摘要: LazyLLM 是商汤大装置推出的开源低代码框架,作为构建和优化多 Agent 应用的一站式开发框架,覆盖应用搭建、数据准备、模型部署、微调、评测等全流程开发环节,提供丰富的工具支持。其以模块化设计打破传统开发壁垒,通过数据流驱动重构开发逻辑,能让开发者用极简代码实现工业级复杂 AI 应用,摆脱冗余编码束缚,聚焦核心业务场景,降低 AI 应用构建成本并支持持续迭代优化。堪称 AI 开发者的 “效率神器”,其技术普惠理念为 AI 开发领域带来新的实践范式,推动了更高效的开发模式。本文将以Python编程为切入点,带你深入了解LazyLLM框架。 LazyLLM 是构建和优化多 Agent 应用的一站式开发工具,为应用开发过程中的全部环节(包括应用搭建、数据准备、模型部署、模型微调、评测等)提供了大量的工具,协助开发者用极低的成本构建 AI 应用,并可以持续地迭代优化效果。 LazyLLM作为商汤大装置推出的开源低代码框架,简直是AI开发者的“效率神器”

By Ne0inhk