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

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

前端异常监控是保障应用稳定性的关键。通过 window.onerror 和 unhandledrejection 可捕获全局错误,但浏览器参数差异导致堆栈信息不完整。设计通用 formatError 函数,优先提取 error.stack,降级处理非 Error 对象及旧版 IE 兼容,结合 navigator.sendBeacon 实现低侵入上报。方案涵盖去重、采样及跨域脚本处理,帮助开发者构建可靠的前端监控体系,快速定位线上问题。

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

引言

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

  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} 格式化后的错误字符串
 */
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. 引言
  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. 总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • Scala 流程控制:分支与循环
  • 2024 AI 大模型面试核心知识点与实战技巧
  • 在 Cursor 中配置 MCP 服务实现自动化开发
  • Flutter 三方库 arcade 的鸿蒙化适配指南
  • FPGA 板上基于 Simulink 与 ModelSim 联合仿真验证的 Buck 闭环设计及调试
  • Whisper-large-v3 与 FunASR 技术选型与性能调优
  • Generative UI 如何重塑 AI 时代的前端交互
  • Glide 加载 WebP 动画的常见问题与解决方案
  • 链表经典算法题详解
  • 基于YOLOv8与LLM的Web目标检测及人脸表情识别系统
  • Python 2026 发展局势:AI 时代的通用基础设施语言
  • Java Web 开发入门:核心概念、环境搭建与 Servlet JSP 实战
  • 离散 PR 控制器原理、C 语言实现及逆变闭环验证
  • 从冯诺依曼体系到操作系统:解析 Linux 底层核心逻辑
  • LangChain 使用知识库修复大模型幻觉问题
  • 支持向量机 (SVM) 原理与代码实例讲解
  • MCP 服务集成实战:browser-tools-mcp 配置教程
  • WebGL 跨端兼容实战:PC 与移动端全适配方案
  • 利用统一 API 平台优化大模型选型与接入流程
  • CS144 课程 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