前端日志上报的新姿势“Beacon”
# 前端日志上报的新姿势“Beacon”

![5621540972482_ pic](https://user-images.githubusercontent.com/3739368/47773548-669e9f00-dd25-11e8-8147-cb8f84071ddb.jpg)

在前端应用越来复杂的今天，为了监控前端应用是否正常运行，通常会在前端收集一些错误与性能等数据，最终我们会将这些数据上报到服务端。

上报的方式有很多，理论上我们只要能把数据发给服务端就行了。在浏览器中可以发送请求的方式非常多，包括不限于：`xhr`、`fetch`、`script`标签、`img`标签、`link`标签、CSS背景图等。

不同的上报方式之间存在很大的差异。目前主流的上报方式是利用`img`标签的`src`属性发送请求，例如：

```javascript
(new Image).src = `/haopv.gif?a=xx&b=xxx`
```
因为日志上报不需要响应处理，只需要把数据发过去就行。并且大部分接收日志的服务器地址与业务方可能不是一个部门，甚至可能不是一个公司，所以会涉及到跨域问题。使用`img`标签的`src`属性既可以把数据发送给服务端又不需要接收响应，同时解决了跨域问题，所以是目前比较受欢迎的日志上报实现方式。

但是这样就真的没问题了么？

日志上报并不是应用的主要功能逻辑，也就是说，日志上报是低优先级的，它不应该与其他高优先级操作（例如：获取关键资源、输入响应、运行动画等）去竞争网络与计算资源（通俗的说就是日志上报行为不应该影响业务逻辑，不应该占用业务计算资源）。但是这种单向请求又负责传递应用的错误与性能数据，所以我们必须要确保它会被交付到服务端。

通常，为了提高交付率，我们会选择立即交付每个收集到的数据，而不是合并与推迟交付。延迟传递可能意味着请求没有足够的时间来成功完成，这可能导致重要的应用数据丢失。并且推迟上报会影响下一个页面的下载与渲染，因为`unload`事件中的代码会阻塞下一个页面渲染。

这就意味着我们的交付行为有可能会被插入到正在忙碌工作的事件循环中，从而抢占了其他高优先级的任务的资源，因为JS是单线程的。这有可能会损害用户体验。

我们如何确保日志数据会被交付的同时，尽可能地减少与其他关键操作的资源争用呢？答案是信标（Beacon）。

## 信标（Beacon）

信标（Beacon）可以异步与非阻塞的数据传输，从而最大限度地减少与其他关键操作的资源争用，同时它可以确保这些请求一定会被处理并将其传递到服务端：

* 信标请求优先避免与关键操作和更高优先级的网络请求竞争。
* 信标请求可以有效地合并，以优化移动设备上的能量使用。
* 保证页面卸载之前启动信标请求，并允许运行完成且不会阻塞请求或阻塞处理用户交互事件的任务。

信标的使用非常简单：

```javascript
var data = JSON.stringify({
  name: 'Berwin'
});
navigator.sendBeacon('/haopv', data)
```

**参数**

* url：上报的目标地址
* data：被上报的数据
* 返回值（Return Value）：sendBeacon方法被执行后返回一个布尔值，`true`代表用户代理成功地将信标请求加入到队列中，否则返回`false`。

> 用户代理对通过信标发送的数据量进行限制，以确保请求被成功传递到服务端，并且对浏览器活动的影响降到最小。如果要排队的数据量超出了用户代理的限制，sendBeacon方法将返回`false`，返回`true`代表浏览器已将数据排队等待传递。然而，由于实际数据传输是异步的，所以此方法不提供任何关于数据传输是否成功的信息。

[虽然信标得到了很高的支持度](https://caniuse.com/#feat=beacon)，但还是无法在所有浏览器中使用，所以如果您想使用信标上报前端日志，一些特征检测是必要的。

还有一个需要注意的是，通过信标发送的请求，请求方法均为`POST`，且不支持修改。

## 总结

日志上报在生产环境下不仅仅是把请求发出去。日志上报并不是主要逻辑所以优先级很低，为了最佳的用户体验，在考虑避免占用业务计算资源和避免竞争业务网络请求的同时我们还要保证数据一定会交付到服务端，最好的方式是尽可能的使用信标（Beacon）。