Skip to content

Latest commit

 

History

History
113 lines (58 loc) · 5.07 KB

File metadata and controls

113 lines (58 loc) · 5.07 KB

$1. 前端如何实现日志收集上报

前端日志收集上报的实现通常涉及到收集、存储、上报这几个核心步骤,并需要考虑数据安全、上报时机、性能影响等因素。

🚀 核心实现方案

  1. 日志收集 (Log Collection)

主要通过重写或劫持浏览器原生方法来实现:

  • console 日志: 重写 console.log/error/warn 等方法,在日志输出到控制台的同时,将日志数据(包括时间、日志级别、内容等)保存起来。

  • 错误/异常捕获:

    • 代码错误: 监听 window.onerror 事件来捕获未被 try...catch 的 JavaScript 运行时错误。
    • Promise 错误: 监听 window.onunhandledrejection 事件来捕获未处理的 Promise 拒绝错误。
    • 资源加载错误: 监听 window.addEventListener('error', ...),并通过事件的 target 来判断是否为资源加载错误。
  • 用户行为日志(埋点): 在关键操作(如点击、页面跳转、曝光)发生时,主动调用日志收集的 API 记录信息。

  1. 日志存储 (Log Storage)

由于浏览器可能不会立即上报日志,通常会先将日志存储在本地,批量上报:

  • 内存缓存 (Buffer): 最简单的方式,将日志先存储在一个 JavaScript 数组(内存队列)中。

  • 本地存储 (Persistent Storage):使用浏览器提供的存储机制,如 IndexedDB 或 localStorage (不推荐,容量和性能受限) 进行持久化存储。

  1. 日志上报 (Log Reporting)

将本地缓存或存储的日志数据发送到后端服务器。

  • Ajax/Fetch 请求: 最常用、灵活的方式,可以自定义请求头、处理响应。

    • 缺点: 在页面关闭前发送请求,如果请求未完成,可能导致日志丢失。
  • navigator.sendBeacon(): 专门设计用于在页面卸载时发送少量数据的 API。

    • 优点: 浏览器保证在不影响页面卸载性能的情况下,异步发送请求,是关闭页面时上报日志的首选方式。

    • 限制: 请求方法为 POST, 不能自定义请求头, 且数据量有限。

💡 总结与建议

在实际项目中,推荐使用成熟的开源日志收集 SDK 或平台(如 Sentry、美团的 Logan Web 等),它们已经解决了大部分上述提到的复杂问题.

如果您需要自己实现:

收集层: 重写 console 和监听 onerror / onunhandledrejection。

存储层: 优先使用 内存队列 配合 定时批量上报。对于需要持久化的,使用 IndexedDB。

上报层: 正常情况下使用 Fetch/Ajax;页面关闭时使用 navigator.sendBeacon()。


$2. 前端如何保证日志的时序性呢

日志的时序性(即保证日志记录的顺序与事件发生的顺序一致)对于问题排查至关重要。

在前端复杂的异步环境中,保证时序性需要从采集、存储、上报三个阶段进行处理

⏱️ 保证日志时序性的方法

  1. 采集阶段:添加高精度时间戳

这是保证时序性的最基本和最关键的一步。在记录任何一条日志时,必须精确记录其发生的时间。

  • 使用 Date.now() 或 performance.now():

    • Date.now(): 返回从 Epoch (1970 年 1 月 1 日 00:00:00 UTC) 开始经过的毫秒数。适用于记录绝对时间。

    • performance.now(): 返回当前时间距离 performance.timing.navigationStart 或其他高精度时间戳的毫秒数(取决于浏览器)。它提供的是高精度相对时间,可以精确到微秒,但需要计算与绝对时间的偏移量。

  • 最佳实践: 记录日志时,同时存储 绝对时间戳 (Date.now()) 和 相对时间戳 (performance.now())。

    • Date.now() 用于跨会话、跨设备的日志对比。

    • performance.now() 用于同一会话内,精确判断短时间内事件的顺序。

  1. 存储阶段:使用严格的队列结构
  • 内存队列 (Array): 将新日志推入数组末尾
  1. 上报阶段:利用时间戳进行排序

由于网络请求的不确定性和并发性,上报请求到达服务器的顺序可能与日志记录的顺序不一致。

因此,日志在打包上报前和服务器端接收后都需要依赖时间戳来保证时序

A. 前端打包上报前

在将多条日志打包成一个批次(Batch)发送之前,进行一次预排序。

步骤:

  • 从队列中取出要上报的日志集合。

  • 根据每条日志的 时间戳(timestamp) 字段进行升序排序。

  • 将排序后的日志数组作为请求体发送。

B. 服务器端接收后

服务器接收到的数据包可能是并发的,但由于每个数据包内的日志已经排序,服务器只需要:

  • 处理跨包时序: 如果日志平台支持,将来自同一用户(通常通过 sessionId 或 deviceId 区分)的多个上报包,按照日志中最早的时间戳或上报时间进行粗略排序。

  • 最终排序: 在日志分析系统(如 ELK Stack 或 ClickHouse)中,将日志写入时,使用采集时携带的高精度时间戳作为最终的排序依据。

$3. 如何处理前端时钟漂移(即用户设备时间不准确)对日志时序性的影响?

策略一:引入服务器时间作为校准基准 (NTP 机制)