本文作者:随风丶逆风

埋点,它的学名是事件追踪(Event Tracking),主要是针对特定用户行为或业务过程进行捕获、处理和发送的相关技术及实施过程。
埋点是产品数据分析的基础,一般用于推荐系统的反馈、用户行为的监控和分析、新功能或者运营活动效果的统计分析等。


埋点包含两个重要概念:事件(event),属性(param)
事件(event):应用中发生了什么,例如用户操作、系统事件或系统错误。以你拍一产品为例,包含以下事件:enter_page(进入页面)、leave_page(离开页面)。
属性(param):为了描述用户群细分而定义的属性,例如语言偏好或地理位置。以“进入课后练习”事件为例,它包含如下事件属性:enter_from(从哪个页面来),class_id(课程id)等。
属性值(value):属性的维度,即行为触发时的具体维度。例如:enter_from:home(主页)、system(系统)等。


主流方案
无痕埋点(全埋点),利用浏览器或APP自带的监听方式,对用户的浏览页面、点击等行为进行收集,一般用于粗颗粒度的数据分析,例如公司的slardar
数据噪声大,不管有用没有,数据都会被收集
无法定制化埋点,无法采集到指定事件和业务属性
可供DA使用的信息较少
接入简单,几乎无侵入,不需要额外的开发成本
用户操作行为收集非常完整,几乎不会遗漏

代码埋点,前端开发人员在代码中自定义监听和收集
工作量大,而且对代码侵入性很大,后期维护也不是很方便
可以精确埋点,具备明确的事件标识
业务属性非常丰富
埋点触发方式可以灵活定义
DA使用更方便和精确

埋点sdk,sdk向外暴露上报埋点的接口,监听和收集过程开发人员无感知。例如公司的tea
暂时想不到
业务开发只需关注事件标识、业务属性等
兼顾无痕埋点优点和代码埋点的优势


常见埋点属性
通常前端是按照页面维度统计埋点的,常见的事件属性如下:

属性

描述

uid

用户id,若用户未登陆,则返回特定标识id

url

当前事件触发页面的url

eventTime

触发埋点的时间戳

localTime

触发埋点时的用户本地时间,使用标准YYYY-MM-DD HH:mm:ss格式表示,方便后期直接使用字符串查询

deviceType

当前用户使用的设备类型,比如apple、三星、chrome等

deviceId

当前用户使用的设备id

osType

当前用户使用的系统类型,比如windows、macos、ios、android等

osVersion

当前用户使用的系统版本

appVersion

当前应用版本

appId

当前应用id

extra

自定义数据,一般是序列化的字符串,且数据结构应保持稳定


常见埋点事件

前端监控gpu 前端监控埋点_javascript


性能数据采集方案
目前性能指标数据大部分来源于 window.performance API。
参数名 描述
connectEnd HTTP(TCP)
返回浏览器与服务器之间的连接建立时的时间戳。如果建立的是持久连接,则返回值等同于fetchStart属性的值。连接建立指的是所有握手和认证过程全部结束。
connectStart HTTP(TCP)
域名查询结束的时间戳。如果使用了持续连接(persistent connection),或者这个信息存储到了缓存或者本地资源上,这个值将和 fetchStart一致。
domComplete
当前文档解析完成,即Document.readyState 变为 'complete’且相对应的readystatechange 被触发时的时间戳
domContentLoadedEventEnd
当所有需要立即执行的脚本已经被执行(不论执行顺序)时的时间戳。
domContentLoadedEventStart
当解析器发送DOMContentLoaded 事件,即所有需要被执行的脚本已经被解析时的时间戳。
domInteractive
当前网页DOM结构结束解析、开始加载内嵌资源时(即Document.readyState属性变为“interactive”、相应的readystatechange事件触发时)的时间戳。
domLoading
当前网页DOM结构开始解析时(即Document.readyState属性变为“loading”、相应的 readystatechange事件触发时)的时间戳。
domainLookupEnd
DNS 域名查询完成的时间。如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
domainLookupStart
DNS 域名查询开始的UNIX时间戳。如果使用了持续连接(persistent connection),或者这个信息存储到了缓存或者本地资源上,这个值将和fetchStart一致。
fetchStart
浏览器准备好使用HTTP请求来获取(fetch)文档的时间戳。这个时间点会在检查任何应用缓存之前。
loadEventEnd
当load事件结束,即加载事件完成时的时间戳。如果这个事件还未被发送,或者尚未完成,它的值将会是0.
loadEventStart
load事件被发送时的时间戳。如果这个事件还未被发送,它的值将会是0。
navigationStart
同一个浏览器上一个页面卸载(unload)结束时的时间戳。如果没有上一个页面,这个值会和fetchStart相同。
redirectEnd
最后一个HTTP重定向完成时(也就是说是HTTP响应的最后一个比特直接被收到的时间)的时间戳。如果没有重定向,或者重定向中的一个不同源,这个值会返回0.
redirectStart 第一个HTTP重定向开始时的时间戳。如果没有重定向,或者重定向中的一个不同源,这个值会返回0。
requestStart
返回浏览器向服务器发出HTTP请求时(或开始读取本地缓存时)的时间戳。
responseEnd
返回浏览器从服务器收到(或从本地缓存读取,或从本地资源读取)最后一个字节时(如果在此之前HTTP连接已经关闭,则返回关闭时)的时间戳。
responseStart
返回浏览器从服务器收到(或从本地缓存读取)第一个字节时的时间戳。如果传输层在开始请求之后失败并且连接被重开,该属性将会被数制成新的请求的相对应的发起时间
secureConnectionStart HTTPS
返回浏览器与服务器开始安全链接的握手时的时间戳。如果当前网页不要求安全连接,则返回0。
unloadEventEnd
和 unloadEventStart 相对应,unload事件处理完成时的时间戳。如果没有上一个页面,这个值会返回0。
unloadEventStart
上一个页面unload事件抛出时的时间戳。如果没有上一个页面,这个值会返回0。


前端监控gpu 前端监控埋点_javascript_02


错误数据采集方案
目前所能捕捉的错误有三种:
资源加载错误,通过 addEventListener(‘error’, callback, true) 在捕获阶段捕捉资源加载失败错误。
js 执行错误,通过 window.onerror 捕捉 js 错误。跨域的脚本会给出 “Script Error.” 提示,拿不到具体的错误信息和堆栈信息。此时需要在script标签增加crossorigin="anonymous"属性,同时资源服务器需要增加CORS相关配置,比如Access-Control-Allow-Origin: *
promise 错误,通过 **addEventListener(‘unhandledrejection’, callback)**捕捉 promise 错误,但是没有发生错误的行数,列数等信息,只能手动抛出相关错误信息。

// 在捕获阶段,捕获资源加载失败错误
addEventListener('error', e => {
    const target = e.target
    if (target != window) {
        monitor.errors.push({
            type: target.localName,
            url: target.src || target.href,
            msg: (target.src || target.href) + ' is load error',
            time: Date.now()
        })
    }
}, true)

// 监听 js 错误
window.onerror = function(msg, url, row, col, error) {
    monitor.errors.push({
        type: 'javascript',
        row: row,
        col: col,
        msg: error && error.stack? error.stack : msg,
        url: url,
        time: Date.now()
    })
}

// 监听 promise 错误 缺点是获取不到行数数据
addEventListener('unhandledrejection', e => {
    monitor.errors.push({
        type: 'promise',
        msg: (e.reason && e.reason.msg) || e.reason || '',
        time: Date.now()
    })
})

数据上报方案

Beacon 接口用来调度向 Web 服务器发送的异步非阻塞请求。
Beacon 请求使用 HTTP POST方法,并且不需要有响应。
Beacon 请求能确保在页面触发 unload 之前完成初始化。
通俗的讲就是,Beacon可将数据异步发送至服务端,且能够保证在页面卸载完成前发送请求(解决ajax页面卸载会终止请求的问题)。使用方法如下:
navigator.sendBeacon(url, data);
其中 data 参数是可选的,它的类型可以为 ArrayBufferView, Blob, DOMString 或者 FormData。如果浏览器成功地将 beacon 请求加入到待发送的队列里,这个方法将会返回 true ,否则将会返回 false

使用Beacon时需要后台需要使用post方法接收参数,考虑到跨域问题,后台还需要改造接口配置CORS。同时请求头必须满足CORS-safelisted request-header,其中content-type的类型必须为application/x-www-form-urlencoded, multipart/form-data, 或者text/plain。

type ContentType = 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/plain';

const serilizeParams = (params: object) => {
    return window.btoa(JSON.stringify(params))
}

function sendBeacon(url: string, params: object) {
  const formData = new FormData()
  formData.append('params', serilizeParams(params))
  navigator.sendBeacon(url, formData)
}

Image
sendBeacon的兼容性问题是不可避免的,不过可以充分利用大部分浏览器会在页面卸载前完成图片的加载的特性,通过在页面添加img的方式上报数据。

function sendImage(url: string, params: object) {
  const img = new Image()

  img.style.display = 'none'

  const removeImage = function() {
    img.parentNode.removeChild(img)
  }

  img.onload = removeImage
  img.onerror = removeImage

  img.src = `${url}?params=${serilizeParams(params)}`

  document.body.appendChild(img)
}