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

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

前端异常监控是保障应用稳定性的关键。通过 window.onerror 和 unhandledrejection 全局监听,结合统一的错误格式化函数,可提取堆栈、位置及环境信息。方案兼容主流浏览器及旧版 IE,支持非 Error 对象处理与循环引用兜底。利用 sendBeacon 实现低侵入上报,配合采样策略减轻服务器压力,为故障排查提供完整上下文。

flc发布于 2026/4/9更新于 2026/10/1986 浏览
前端异常捕获与统一格式化:从 console.log 到服务端上报

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

为什么需要统一格式化?

在前端开发中,异常监控是保障应用稳定性的关键一环。当用户遇到页面白屏、功能不可用等问题时,如果能及时收集到详细的错误信息(包括堆栈、行列号、浏览器环境等),就能快速定位并修复 bug。

浏览器提供了 window.onerror 和 unhandledrejection 两个全局事件,分别用于捕获未处理的 JavaScript 异常和未捕获的 Promise 拒绝。然而,不同浏览器对这些事件的参数支持存在差异,错误对象的格式也各不相同。如何编写一个兼容所有浏览器、并能像 console.log(error) 那样输出完整堆栈的格式化函数,是搭建前端监控系统的第一步。

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

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

console.log(error) 的底层原理

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

  1. console.log 接收一个对象后,会调用该对象的 [Symbol.toStringTag] 或自定义的 inspect 方法。对于 Error 对象,V8 内部会检查其是否有 stack 属性。
  2. error.stack 是一个非标准但所有现代浏览器都支持的属性,它包含了当前调用栈的快照。这个堆栈字符串的生成依赖于运行时自动收集的调用帧。
  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} 格式化后的错误字符串
 */
function formatError(error, fallbackMessage, url, line, col) {
  let result = '';

  // 情况 1:error 是对象类型,尝试提取 stack 或 message
  if (error && typeof error === 'object') {
    // 优先使用 stack(包含完整的调用堆栈)
    if (typeof error.stack === 'string') {
      result = error.stack;
    }
    // 其次使用标准 error 属性(name 和 message)
    else if (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:仍然没有有效内容,使用 fallbackMessage
  if (!result && fallbackMessage) {
    result = fallbackMessage;
  }

  return result;
}
关键点说明
  • 优先使用 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 与鸿蒙融合:游戏形态的变革与未来趋势
  • 企业级 AI 大模型私有化部署指南与方案
  • AI 绘画入门:从零掌握文生图核心技术
  • OpenClaw SOUL.md:用自然语言定义 AI Agent 的身份与边界
  • 主流无人机厂商 RemoteID 支持情况汇总
  • Neo4j Aura Agent 正式上线:图谱驱动的智能体构建新范式
  • JavaSE 核心知识点总结
  • 数据结构:双向循环链表详解
  • VLM Unlearning 技术路线论文阅读总结与梳理
  • CS61B 课程笔记:图结构基础与深度优先遍历
  • Python+AI 入门完整指南
  • 前端三个月速成学习路线与面试核心考点
  • 利用 AI 工具将 Python 代码转换为 C++ 提升性能
  • openclaw webUI 空白页问题
  • JDK 版本切换导致 toString() 空指针异常排查与解决
  • OpenClaw WebUI 空白页问题及配置修复
  • Java volatile 关键字解析:底层原理与最佳实践
  • C++ 手写 List 容器:双向链表原理及实现
  • 阿里健康医药 B2B 团队敏捷转型实践
  • Java Web 开发:数据库知识复习与整理

相关免费在线工具

  • 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