跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaScript大前端

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

前端异常监控是保障应用稳定性的关键。通过 window.onerror 和 unhandledrejection 可捕获全局错误,但不同浏览器参数差异大,直接上报往往信息不全。深入分析 console.log 底层原理,设计兼容多浏览器的错误格式化函数,优先提取 stack 属性,降级处理非 Error 对象及旧版 IE 场景。结合 sendBeacon 实现低侵入上报,并探讨采样、去重及跨域堆栈等进阶策略,帮助开发者构建可靠的前端监控体系。

游戏玩家发布于 2026/4/5更新于 2026/7/2039 浏览
前端异常捕获与统一格式化:从 console.log 到服务端上报

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

在前端开发中,异常监控是保证应用稳定性的重要一环。当用户遇到页面白屏、功能不可用等问题时,如果能及时收集到详细的错误信息(包括堆栈、行列号、浏览器环境等),就能快速定位并修复 bug。浏览器提供了 window.onerror 和 unhandledrejection 两个全局事件,分别用于捕获未处理的 JavaScript 异常和未捕获的 Promise 拒绝。然而,不同浏览器对这些事件的参数支持存在差异,错误对象的格式也各不相同,如何编写一个兼容所有浏览器、并能像 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 引擎为例:

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

关键点说明

  • 优先使用 error.stack:只要错误对象有 stack 属性,就直接使用它,因为 stack 已经包含了最完整的调用链和位置信息。
  • 降级使用 name 和 message:如果对象是 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 参数,此时 error 为 undefined,我们的 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.sendBeacon 或 fetch 发送到后端接口。为了不影响用户体验,建议使用 sendBeacon,它会在页面卸载时也能确保请求发出。

function sendToServer(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 不存在时,利用 message、source、lineno 构造一个简化版本。

堆栈格式差异

不同浏览器生成的 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';

  function formatError(error, fallbackMessage, url, line, col) {
    let result = '';
    if (error && typeof error === 'object') {
      if (typeof error.stack === 'string') {
        result = error.stack;
      } else if (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;
  }

  function sendToServer(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.onerror 和 unhandledrejection 实现了全局异常捕获与上报。这个方案能够像原生控制台一样输出完整的错误堆栈,同时处理了各种边界情况(非 Error 对象、旧版 IE、循环引用等)。将此模块集成到项目中,你就拥有了一个可靠的前端监控基础,为后续的故障排查和数据分析奠定坚实的基础。

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

目录

  1. 前端异常捕获与统一格式化:从 console.log 到服务端上报
  2. 为什么需要统一格式化?
  3. console.log(error) 的底层原理
  4. 统一错误格式化函数
  5. 关键点说明
  6. 全局监听器:window.onerror 和 unhandledrejection
  7. window.onerror
  8. unhandledrejection
  9. 上报函数实现
  10. 兼容性深度解析
  11. 不同浏览器的 window.onerror 参数
  12. 堆栈格式差异
  13. 完整示例代码
  14. 进阶考虑
  15. 1. 去重与聚合
  16. 2. 错误采样
  17. 3. 附加上下文
  18. 4. 跨域脚本的堆栈
  19. 总结
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 基于腾讯云 HAI 与 DeepSeek 快速搭建个人网页
  • 低代码可视化系统核心对象详解:数据、请求与寄连
  • Windows WSL Ubuntu 安装 OpenClaw 配置飞书与百炼模型全流程
  • LLM 推理与训练 GPU 显存估算方法详解
  • 开源知识库 RAGFlow 部署与实战操作详解
  • 前端国际化实战:基于 i18next 的多语言方案
  • Linux 文件描述符与重定向实战:从原理到 minishell 实现
  • 解决复杂查询难题:通过 Self-querying Prompting 优化 RAG 系统效率
  • 综合评价模型:层次 - 熵权 - 变异系数 - 博弈组合法 Python 实现
  • Graphite Whisper 配置:Carbon 发送间隔与归档策略 AI 建议
  • 2026 主流免费 AI 写作工具评测与去机翻优化方案
  • CSS 基础语法、选择器及常用属性指南
  • 华为昇腾 910B 使用 LLaMA-Factory 微调 Qwen3.5-32B 模型
  • C/C++ 输入输出实战:OJ 场景与性能优化
  • Faster-Whisper-GUI 日语长音频语音识别优化方案
  • 一切皆是映射:深入理解 DQN 的稳定性与收敛性
  • Python 异步编程与协程实战指南
  • 昇腾 NPU 部署 Llama 2 模型:性能测试与实战优化
  • 两数之和:暴力枚举与哈希表优化
  • RPC 原理与 BRPC 实战:基于 C++ 的分布式通信实现

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online