什么,前端日志上报你连这些都不考虑?

4,691 阅读4分钟

前言

小编在速通了好多家中小厂后,在自信心的驱使下,对大厂发起攻势,经历了三次大厂面试的告警平台拷打后,决定好好沉淀并记录告警平台学习日志......,如果有什么学习建议也希望各位能在评论区指导指导,大家一起进步。

正文

上一期介绍了如何收集异常信息,按部就班这一期就讲讲如何将收集到的信息进行上报......

上报方式选择

面试官第一关:“为什么要选择 image 的方式去上报数据?仔细讲讲”

常见的请求方式有以下三种: XHR、image、sendBeacon,那应该如何选择上报方式呢?小编带着大家先来分析一下:

1、 XHR ---> 可以在不刷新页面的情况下请求特定 URL,获取数据。

  • XHR 可选择同步/异步发送请求,需要处理服务端响应,同步:阻塞主线程执行;异步:当页面关闭时未发送的请求会取消,导致数据丢失。

2、image ---> 通过设置 src 属性发送请求。

  • image 不需要服务器响应,不需要携带域名等数据,使用 GET 请求方法

3、sendBeacon ---> 异步地将包含少量数据的请求发送到 Web 服务器。

  • 基于浏览器的传输队列发送,脱离了原本的页面,使用 POST (相对GET来说并不友好)

通过对比我们发现 image 的请求方式更适合我们当下的情况,很好我们已经闯过一关了。

面试官面不改色地开始第二关:“具体讲讲你的上报流程,你是怎么去设计的”

上报流程

采用发布订阅者模式,设计一个订阅中心(handlers),订阅事件(添加埋点的原生方法)并注入回调 ---> 创建监听事件 triggerHandler(当收集到新数据时触发) ---> 执行注入的回调(该回调包含格式化数据、添加用户行为、发送数据),具体流程如下:

whiteboard_exported_image.png

接下来小编以 xhr 举例,手把手带大家一起实现这个流程:

// 订阅中心 handlers
const handlers = {};

// 订阅事件
// handler: { type: 'eventType', callback: function() {} }
function subscribeEvent(handler) {
  handlers[handler.type] = handlers[handler.type] || []
  handlers[handler.type].push(handler.callback)
  return true
}

// 添加订阅
export function addReplaceHandler(handler) {
  if (!subscribeEvent(handler)) return
  // 这里以 xhr 举例
  xhrReplace()
}

// 触发监听事件时执行回调
export function triggerHandlers(type, data) {
  if (!type || !handlers[type]) return
  handlers[type].forEach((callback) => {
    try{
      callback(data)
    }
    catch(e) {
      console.error(`监听事件triggerHandlers的回调函数发生错误\nType:${type}\nError: ${e}`)
    }
  })
}

看到这里订阅中心就完成了,现在需要在收集到新数据时添加埋点,并将数据上报,如果还不知道如何收集数据的,可以先看我上一期的文章,传送门:不是,前端异常信息你还不会收集?,我们依然以 xhr 为例:

image.png

接着将收集到的数据通过 image 方式发送,因为要考虑上报时机,所以将 image 请求封装为函数

export class transportData{
  // 通过队列缓存请求
  constructor() {
    this.url = 'http://localhost:3000/api/report'
    this.queue = new Queue()
  }

  // 封装image请求
  imgRequest(data, url) {
    const requestFun = () => {
      let img = new Image()
      const spliceStr = url.indexOf('?') === -1 ? '?' : '&'
      img.src = `${url}${spliceStr}data=${JSON.stringify(data)}`
      img = null
    }
    this.queue.addFn(requestFun)
  }

  async send(data) {
    this.imgRequest(data, this.url)
  }
}

这样一个收集 xhr 错误信息并上报的流程就完成了。

面试官迎面而来的第三关:“你说要考虑上报时机,你是怎么去规划上报时机的?”

上报时机

看来面试官已经进入我的陷阱了,应该在什么时候进行上报才不会阻塞原有任务的执行?小编突然想到 node 的事件循环:宏任务 -> 微任务 -> render -> 宏 -> 微......,刚好我们可以利用这一特性,将上报过程转为微任务,转为微任务的方式我想大家第一个想到应该是 promise.then,没错小编也是,不过小编还了解到另一种转换方式:queueMicrotask ---> 将微任务加入队列以在控制返回浏览器的事件循环之前的安全时间执行。

整理完思路后,接下来就是 showTime 辣

export class Queue {
  queue = new Set(); // 初始化队列
  isFlushing = false; // 当前是否有微任务正在执行
  micro = 
    typeof queueMicrotask !== 'undefined' 
      ? queueMicrotask 
      : microtask.then;

  addFn = (fn) => {
    if (typeof fn !== 'function') return;
    queue.add(fn);
    if (!isFlushing) {
      isFlushing = true;
      micro(() => flushStack());
    }
  };
  // 支持手动上报
  flushStack() {
    const temp = this.stack.slice(0)
    this.stack.length = 0
    this.isFlushing = false
    for (let i = 0; i < temp.length; i++) {
      temp[i]()
    }
  }
}

恭喜大家,顺利通过第三关!!!

实现效果如图:

image.png

总结

本文从如何选择上报方式 -> 通过发布订阅模式上报数据 -> 选择合适的上报时机,介绍了完整的日志上报链路以及架构的实现,本栏目已经梳理通了收集到上报的流程,监控已经被我们实现了一部分了,强大的监控还需要各项指标的收集、页面卡顿崩溃的监测等等...留个悬念,猜猜下一期要实现什么内容呢,我们下期见。