共计 8820 个字符,预计需要花费 23 分钟才能阅读完成。
什么是埋点
埋点,它的学名是事件追踪(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 | 自定义数据,个别是序列化的字符串,且数据结构应保持稳定 |
常见埋点事件
事件 | 上报机会 | 形容 |
---|---|---|
页面停留 | 以后页面切换或者页面卸载时 | 记录前一页浏览工夫 |
pv | 进入页面时 | 页面拜访次数,uv 只须要依据 deviceId 过滤 |
交互事件 | 用户交互事件触发时 | 比方点击、长按等 |
逻辑事件 | 合乎逻辑条件时 | 比方登陆、跳转页面等 |
性能数据采集计划
目前性能指标数据大部分来源于 window.performance API。
Performance.timing
参数名 | 形容 |
---|---|
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。 |
常见性能指标
指标名 | 形容 |
---|---|
FP | 页面首次绘制工夫 |
FCP | 页面首次有内容绘制的工夫 |
FMP | 页面首次无效绘制工夫,FMP >= FCP |
TTI | 页面齐全可交互工夫 |
FID | 页面加载阶段,用户首次交互操作的延时工夫 |
MPFID | 页面加载阶段,用户交互操作可能遇到的最大延时工夫 |
LOAD | 页面齐全加载的工夫(load 事件产生的工夫) |
FP
FP (First Paint)指标通常会反映页面的白屏工夫,而白屏工夫会反映以后 Web 页面的网络加载性能状况,当加载性能十分良好的状况下,白屏的工夫就会越短,用户期待内容的工夫就会越短,散失的概率就会升高。
该指标能够通过 performance.getEntriesByType('paint')
办法获取 PerformancePaintTiming API
提供的打点信息,找到 name 为 first-paint 的对象,形容的即为 FP 的指标数据,如下图所示:
FCP
FCP (First Contentful Paint) 为首次有内容渲染的工夫点,在性能统计指标中,从用户开始拜访 Web 页面的工夫点到 FCP 的工夫点这段时间能够被视为无内容工夫,个别 FCP >= FP。
该指标能够通过 performance.getEntriesByType('paint')
办法获取 PerformancePaintTiming API 提供的打点信息,找到 name 为 first-contentful-paint 的对象,形容的即为 FCP 的指标数据,如下图所示:
FMP
FMP(First Meaningful Paint),即首次绘制有意义内容的工夫,当整体页面的布局和文字内容全副渲染实现后,即可认为是实现了首次有意义内容的绘制。所以 FMP 掂量了用户看到网页的次要内容的工夫,是用户体验角度的一种重要的掂量指标。
前端业界当初比拟认可的一个计算 FMP 的形式就是「页面在加载和渲染过程中最大布局变动之后的那个绘制工夫」。可通过 MutationObserver 监听每一次页面整体的 DOM 变动,触发 MutationObserver 的回调,在回调计算出以后 DOM 树的变动分数,分数变动最激烈的时刻,即为 FMP 的工夫点。
TTI
TTI(Time To Interactive),即 从页面加载开始到页面处于齐全可交互状态所破费的工夫。页面处于齐全可交互状态时,满足以下 3 个条件:
- 页面曾经显示有用内容。
- 页面上的可见元素关联的事件响应函数曾经实现注册。
- 事件响应函数能够在事件产生后的 50ms 内开始执行。
资源加载指标
window.performance.getEntriesByType('resource')
会返回以后页面加载的所有资源(js、css、img…)的各类性能指标,可用于动态资源性能数据采集。
次要类型有:script、link、img、css、xmlhttprequest、beacon、fetch、other。PerformanceResourceTiming – Web APIs | MDN
参数名 | 形容 |
---|---|
connectEnd | 一个 DOMHighResTimeStamp,示意浏览器实现建设与服务器的连贯以检索资源之后的工夫。 |
connectStart | 一个 DOMHighResTimeStamp,示意浏览器开始建设与服务器的连贯以检索资源之前的工夫。 |
decodedBodySize | 一个 number,示意在删除任何利用的内容编码之后,从音讯主体的申请(HTTP 或缓存)中接管到的大小(以八位字节为单位)。 |
domainLookupEnd | 一个 DOMHighResTimeStamp,示意浏览器实现资源的域名查找之后的工夫。 |
domainLookupStart | 一个 DOMHighResTimeStamp,示意在浏览器立刻开始资源的域名查找之前的工夫 |
duration | 返回一个 timestamp,即 responseEnd 和 startTime 属性的差值。 |
encodedBodySize | 一个 number,示意在删除任何利用的内容编码之前,从无效内容主体的申请(HTTP 或缓存)中接管到的大小(以八位字节为单位)。 |
entryType | 返回 “resource”。 |
fetchStart | 一个 DOMHighResTimeStamp,示意浏览器行将开始获取资源之前的工夫。 |
initiatorType | 一个 string,代表启动性能条目标资源的类型 |
name | 返回资源 URL。 |
nextHopProtocol | 一个 string,代表用于获取资源的网络协议,由 ALPN 协定 ID(RFC7301)定义。 |
redirectEnd | 一个 DOMHighResTimeStamp,示意收到上一次重定向响应的发送最初一个字节时的工夫。 |
redirectStart | 一个 DOMHighResTimeStamp 代表启动重定向的申请开始之前的工夫。 |
requestStart | 一个 DOMHighResTimeStamp,示意浏览器开始向服务器申请资源之前的工夫。 |
responseEnd | 一个 DOMHighResTimeStamp,示意在浏览器接管到资源的最初一个字节之后或在传输连贯敞开之前(以先到者为准)的工夫。 |
responseStart | 一个 DOMHighResTimeStamp,示意浏览器从服务器接管到响应的第一个字节后的工夫。 |
secureConnectionStart | 一个 DOMHighResTimeStamp,示意浏览器行将开始握手过程以爱护以后连贯之前的工夫。 |
serverTiming | 一个 PerformanceServerTiming 数组,蕴含服务器计时指标的 PerformanceServerTiming 条目。 |
startTime | 返回一个 timestamp,示意资源获取开始的工夫。该值等效于 fetchStart。 |
transferSize | 一个 number 代表所获取资源的大小(以八位字节为单位)。该大小包含响应标头字段以及响应无效内容主体。 |
workerStart | 一个 DOMHighResTimeStamp,如果服务 Worker 线程曾经在运行,则返回在分派 FetchEvent 之前的工夫戳,如果尚未运行,则返回在启动 Service Worker 线程之前的工夫戳。如果服务 Worker 未拦挡该资源,则该属性将始终返回 0。 |
其余指标计算形式
指标名 | 形容 | 计算形式 |
---|---|---|
DNS 查问 | DNS 阶段耗时 | domainLookupEnd – domainLookupStart |
TCP 连贯 | TCP 阶段耗时 | connectEnd – connectStart |
SSL 建连 | SSL 连接时间 | connectEnd – secureConnectionStart |
首字节网络申请 | 首字节响应工夫(ttfb) | responseStart – requestStart |
内容传输 | 内容传输,Response 阶段耗时 | responseEnd – responseStart |
DOM 解析 | Dom 解析工夫 | domInteractive – responseEnd |
资源加载 | 资源加载 | loadEventStart – domContentLoadedEventEnd |
首字节 | 首字节 | responseStart – fetchStart |
DOM Ready | dom ready | domContentLoadedEventEnd – fetchStart |
redirect 工夫 | 重定向工夫 | redirectEnd – redirectStart |
DOM render | dom 渲染耗时 | domComplete – domLoading |
load | 页面加载耗时 | loadEventEnd – navigationStart |
unload | 页面卸载耗时 | unloadEventEnd – unloadEventStart |
申请耗时 | 申请耗时 | responseEnd – requestStart |
白屏工夫 | 白屏工夫 | domLoading – navigationStart |
谬误数据采集计划
目前所能捕获的谬误有三种:
- 资源加载谬误,通过
addEventListener('error', callback, true)
在捕捉阶段捕获资源加载失败谬误。 - js 执行谬误,通过
window.onerror
捕获 js 谬误。 -
- 跨域的脚本会给出 “Script Error.” 提醒,拿不到具体的错误信息和堆栈信息。此时须要在 script 标签减少
crossorigin="anonymous"
属性,同时资源服务器须要减少 CORS 相干配置,比方Access-Control-Allow-Origin: *
- 跨域的脚本会给出 “Script Error.” 提醒,拿不到具体的错误信息和堆栈信息。此时须要在 script 标签减少
- 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()})
})
数据上报计划
在这个场景中,须要思考两个问题:
- 如果数据上报接口与业务零碎应用同一域名,浏览器对申请并发量有限度,所以存在网络资源竞争的可能性。
- 浏览器通常在页面卸载时会疏忽异步 ajax 申请,如果须要必须进行数据申请,个别在 unload 或者 beforeunload 事件中创立同步 ajax 申请,以此提早页面卸载。从用户侧角度,就是页面跳转变慢。
Beacon
能够看到,除开 ie 浏览器,目前支流古代浏览器对 beacon 的支持率十分高。Beacon – MDN 文档
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)
}
因为 img 图片为 get 申请形式,不同服务器针对 uri 的长度有限度,长度超过限度时会呈现 HTTP 414 谬误,所以还要留神上报频率,缩小一次性上传的属性过多。
HTTP 1.1 defines Status Code 414 Request-URI Too Long for the cases where a server-defined limit is reached. You can see further details on RFC 2616. For the case of client-defined limits, there is no sense on the server returning something, because the server won’t receive the request at all.
兼容计划
优先应用 sendBeacon 的形式,Image 形式作为 fallback。
function sendLog(url: string, params: object) {if(navigator.sendBeacon) {sendBeacon(url, params)
} else {sendImage(url, params)
}
}