UNPKG

spy-client

Version:

spy client

940 lines (764 loc) 26.5 kB
# spy-client [![Build Status](https://travis-ci.com/kaivean/spy-client.svg?branch=master)](https://travis-ci.com/kaivean/spy-client) ## 介绍 日志采集模块,提供一系列方便的api供使用 1. 新版2.x部分API不再兼容1.x 2. 从2.1.0版本开始,不再兼容IE8及以下IE浏览器 3. 从2.1.8版本开始,兼容小程序环境(new Image类发送);通过继承类,覆盖request方法,可以支持Node.js/跨端框架/小程序环境 ## 安装 ``` npm install spy-client --save ``` CDN方式 不是一次性3个JS都引入,具体往下看 ```html <!--增强版SDK--> <script src="https://code.bdstatic.com/npm/spy-client@2.1.8/dist/spy-client.min.js" type="text/javascript"></script> <!--增强版SDK spy-head--> <script src="https://code.bdstatic.com/npm/spy-client@2.1.8/dist/spy-head.min.js" type="text/javascript"></script> <!--基础版SDK--> <script src="https://code.bdstatic.com/npm/spy-client@2.1.8/dist/spy-client-basic.min.js" type="text/javascript"></script> ``` > 如果对于一些指标想理解更准确,看源码是最佳方式 [SDK源码](https://github.com/kaivean/spy-client) > SDK的指标采集请酌情选用,不要一股脑全用上,如果只用了一项采集功能,但SDK体积太大,可以考虑自行编译,看文档最后 ## 快速使用 初始化 ```javascript const SpyClient = require('spy-client'); const spy = new SpyClient({ pid: '1_1000', // 必须 lid: '', // 可选,页面的logid sample: 1 // 可选,默认为1, 全局抽样,取值:[0-1], 所有发送接口都受到该抽样,单个发送接口的sample配置会覆盖该抽样。 }); ``` 发送性能日志 ```javascript // 发送性能日志 spy.sendPerf({ // 可选, 分组,默认common,用户自定义 group: 'test', // 必须, 指标信息,每个字段为一个指标,由用户自定义,这里的fisrtScreen、whiteScreen等都是业务自己定义,后续会在平台上配置好,平台会从该字段取对应指标信息。 // 这些指标需要你自行计算好时间再发送,不能带单位 info: { tcp: 1200, domReady: 600 }, // 可选,维度信息,每个字段为一个维度,由用户自定义,这里的netType、pageType都是业务自己定义,后续会在平台上配置好,平台会从该字段取对应维度信息。 dim: { os: 'ios', netType: 'wifi' } }); ``` ## SDK说明 SDK分两种 * 基础版SDK:提供最基础和最简单的功能,如果这些功能能满足你,那么直接使用该SDK即可,因为体积较小 * 增强版SDK:除了基础版SDK功能外,集合了丰富的常用的性能和异常指标统计 接下来分别介绍 ## 基础版SDK 提供最基础和最简单的功能,如果这些功能能满足你,那么直接使用该SDK即可 ```javascript // basic spy-client 基本用法,最简单功能 const SpyClient = require('spy-client/dist/spy-client-basic'); const spy = new SpyClient({ pid: '1_1000', // 必须 lid: '', // 可选,页面的logid sample: 1 // 可选,默认为1, 全局抽样,取值:[0-1], 所有发送接口都受到该抽样,单个发送接口的sample配置会覆盖该抽样。 }); ``` 以下先简单列举所有可用API示例 ```javascript // 发生性能日志,本质是数值型的metric数据 spy.sendPerf({ // 可选, 分组,默认common,用户自定义 group: 'test', // 必须, 指标信息,每个字段为一个指标,由用户自定义,这里的fisrtScreen、whiteScreen等都是业务自己定义,后续会在平台上配置好,平台会从该字段取对应指标信息。 // 这些指标需要你自行计算好时间再发送,不能带单位 info: { tcp: 1200, domReady: 600 }, // 可选,维度信息,每个字段为一个维度,由用户自定义,这里的netType、pageType都是业务自己定义,后续会在平台上配置好,平台会从该字段取对应维度信息。 dim: { os: 'ios', netType: 'wifi' } }); // 发送异常日志 spy.sendExcept({ // 必须, 异常信息,msg字段是必须的,是异常唯一标识。其他字段作为补充信息,由用户自定义 info: { msg: 'abc is not undefined', // msg字段是必须的,必须的,必须的,会统计相同msg的总量 stack: 'xxxxx', file: 'xxxxxxx' }, // 可选, 分组,默认common,用户自定义 group: 'test', // 可选,维度信息,每个字段为一个维度,由用户自定义 dim: { os: 'ios' } }); // 发送分布日志 spy.sendDist({ info: { from: 'hao123' }, dim: { os: 'ios' } }); // 发送计数日志 spy.sendCount({ info: { from: 'hao123' }, dim: { os: 'ios' } }); // 如果能拿到error实例,通过该方法快速上报异常,默认会获取stack等信息 spy.sendExceptForError(new Error('error'), { dim: { os: 'ios' } }); // 最基础的API,需要自行指定type字段 spy.send({ type: 'perf' info: { domReady: 1000 }, dim: {} }); // 统计辅助方法 spy.startMark('playTime'); let time = spy.endMark('playTime'); console.log(time); // output: 1000 spy.startMark('pauseTime'); spy.endMark('pauseTime'); // 假设中间执行花费1s console.log(spy.getAllMark()); // output // { // playTime: 1000, // pauseTime: 1000 // } spy.clearMark('pauseTime'); // 清除pauseTime spy.clearAllMark(); // 清除所有mark的信息 ``` 基础版可支持小程序/Node.js/跨端框架环境 1 . 小程序 不用做任何修改,就支持采用new Image发送日志。 2 . Node.js/跨端框架环境 Node.js,跨端框架,以及小程序环境中若采用`spy.send(xxx, true)`方式,则需要继承SpyClient类,覆盖request方法. 如果是Node.js,需要服务器有外网权限 ```javascript const SpyClient = require('spy-client/dist/spy-client-basic'); // 若环境编译不支持umd,则可以导入es module // const SpyClient = require('spy-client/dist/spy-client-basic.esm'); class SpyClientNode from SpyClient { request(url: string, data?: any) { axios({ method: data ? 'post' : 'get', url, data: data ? JSON.stringify(data) : data, }); } } const spy = new SpyClientNode({ pid: '1_1000', // 必须 lid: '', // 可选,页面的logid sample: 1 // 可选,默认为1, 全局抽样,取值:[0-1], 所有发送接口都受到该抽样,单个发送接口的sample配置会覆盖该抽样。 }); spy.sendPerf({ info: { responseTime: 200 } }); ``` ## 增强版SDK 增强版SDK分成了2部分 1. spy-head:有些功能我们希望越早生效越好,比如全局JS报错监控。因此把这些功能最小集抽成一个单独JS,以便可以插入head标签内,也不会全量引入整个SDK在头部。当然,放到任何地方都是可以,开发者自行决策即可。此部分包含的功能有 * 异常:全局JS报错监控、资源加载失败监控、白屏异常监控 * 性能:Longtask等信息采集,真正的统计是在spy-client里,只是越早采集,能获取更多的longtask 2. spy-client:此部分提供了丰富的性能和异常的指标统计,其中部分功能依赖于spy-head,包含的功能有 * 性能指标采集:包含体积、卡顿、速度等60+个性能指标采集方法 * 异常:包含大于150K的大图片采集、HTTPS环境下HTTP资源采集 * 辅助方式: mark系列辅助方法 > 增强版SDK仅支持浏览器环境 ### spy-head使用 spy-head JS可以视情况通过script内联或嵌入其他JS里 > 如果要启用一项异常监控功能,需要设置其抽样sample不为0 ```html <script> // spy-head js可以视情况通过script内联或外链,外链地址可见文档开头的CDN const spyHead = require('spy-client/dist/spy-head'); spyHead.init({ pid: '1_1', // spy申请的pid lid: '', // 业务的log id,可选 // 数据类型:异常,触发时间:监听的window.addEventListen('error')有资源加载失败时 // 上报信息里包含资源的标签名,资源地址,xpath // 用不着的话,需要删掉这个配置 resourceError: { // 发送的分组名称,可以自定义 group: 'resource', // 抽样,禁用可以设置为0 sample: 1, // 对发送之前的数据进行操作,如果不想发送,返回false即可 // 如果想增加维度,可以自定加上data.dim字段 // 用不着的话,可以删掉这个函数 handler: function (data) { } }, // 数据类型:异常,触发时间:监听的全局报错window.addEventListen('error'),有未被捕获的全局异常抛出时 // 上报信息里包含错误message,stack,之前已发生的所有错误等 // 用不着的话,需要删掉这个配置 jsError: { // 发送的分组名称,可以自定义 group: 'js', // 抽样,禁用可以设置为0 sample: 1, // 对发送之前的数据进行操作,如果不想发送,返回false即可 // 如果想增加维度,可以自定加上data.dim字段 // 用不着的话,可以删掉这个函数 handler: function (data) { } }, // 数据类型:异常,触发时间:OnJudgeReturnFalseWhenTimeout // 上报信息里包含之前已发生的所有错误、dns、tcp、请求响应时间(可能为负,说明该过程没有完成)、设备信息等 // 用不着的话,需要删掉这个配置 whiteScreenError: { // 抽样,禁用可以设置为0 sample: 1, // 发送的分组名称,可以自定义 group: 'whiteScreen', // 一旦以下逻辑不满足,就认为白屏:document.querySelector(selector)的元素包含 document.querySelector(selector).querySelector(subSelector) 并且 document.querySelector(selector) 的高度大于屏幕高度的2/3 selector: 'body', subSelector: 'button1', // 单位ms,在timeout后,执行上述检测 timeout: 6000, // 对发送之前的数据进行操作,如果不想发送,返回false即可 // 如果想增加维度,可以自定加上data.dim字段 // 用不着的话,可以删掉这个函数 handler: function(data) { } } }); <script> ``` > 像Vue等组件框架会对组件代码做try catch,然后打印到console,这种错误情况是不会有全局错误的,即window.onerror不会触发。只能使用框架的全局error回调(比如Vue.config.errorHandler)去拿到错误信息,再调用spy.sendExcept上报。 > 所以在发现上述jsError全局异常监听失效时,请先自行调试是否能通过window.addEventListen('error')监听到 ### 主体SDK spy-client ```javascript // enhanced spy-client const SpyClient = require('spy-client'); const spy = new SpyClient({ pid: '1_1000', // 必须 lid: '', // 可选,页面的logid sample: 1 // 可选,默认为1, 全局抽样,取值:[0-1], 所有发送接口都受到该抽样,单个发送接口的sample配置会覆盖该抽样。 }); ``` #### 基于performance timing的基本指标 ```javascript // 类型:性能,触发时间:500MsAfterOnLoad,说明:performance timing的数据采集基本指标 spy.listenTiming(function (metric) { spy.sendPerf({ info: metric }); }); ``` metric定义 ```typescript export interface TimingMetric { // dns解析 dns: number; // tcp链接 tcp: number; // 主文档请求 request: number; // 主文档响应时间 response: number; // DOM解析时间:Dom解析开始到结束时间 // 这是从页面部分数据返回,浏览器开始解析doc元素到最底部的script脚本解析执行完成 // 脚本里触发的异步方法或绑定了更靠后的事件,不再纳入范围内 parseHtml: number; // DOM解析完成总时间:页面开始加载到Dom解析结束 // 很多事件绑定是在domContentLoaded事件里的,所以等其结束,一般页面元素的事件绑定好了,用户可以正确交互 // 当然存在在该加载事件之后绑定元素事件情况,但不再此考虑范围内 domReady: number; // 处理所有注册的load事件函数的时间 loadEventHandle: number; // onload完成时间 // 基本该做的都做完,资源也都加载完成了 // 当然在onload事件处理函数里启动了异步方法,不再纳入范围内 load: number; // first-paint https://w3c.github.io/paint-timing/#sec-PerformancePaintTiming fp?: number; // first-contentful-paint https://w3c.github.io/paint-timing/#sec-PerformancePaintTiming fcp?: number; // T7内核计算的首次绘制, 单位ms t7FirstPaint?: number; // T7内核计算的首屏时间, 单位ms t7FirstScreen?: number; } ``` #### Largest Contentful Paint 最大块内容绘制完成时间 * 依赖:spy-head.js ```javascript // 类型:性能,触发时间:500MsAfterOnLoad spy.listenLCP(function (metric) { spy.sendPerf({ info: metric }); }); ``` metric定义 ```typescript export interface LCPMetric { // Largest Contentful Paint https://web.dev/lcp/ // 在onload时间内最大块内容绘制完成时间 lcp: number; } ``` #### FID 首次输入延迟,衡量首次交互卡顿 * 依赖:spy-head.js ```javascript // 类型:性能,触发时间:OnInput spy.listenFID(function (metric) { spy.sendPerf({ group: 'fid', info: metric }); }); ``` metric定义 ```typescript export interface FIDMetric { // First Input Delay https://web.dev/fid/ // 首次输入延迟 fid: number; } ``` #### TTI 用户可完全交互时间 ```javascript // 数据类型:性能,触发时间:当第一次出现5s内没有网络请求(默认排查了gif请求),且没有longtask产出时 spy.listenTTI(function (metric) { spy.sendPerf({ group: 'tti', info: metric }); }); ``` metric定义 ```typescript export interface TTIMetric { // Time to Interactive https://web.dev/tti/ // 用户可完全交互时间 tti: number; } ``` #### 获取资源信息 获取资源的本身大小、传输大小、数量、传输、缓存率 ```javascript // 数据类型:性能,触发时间:500MsAfterOnLoad spy.listenResource(function (metric, hostMetric) { spy.sendPerf({ info: metric }); // 分域名进行统计的 console.log('hostMetric', hostMetric); }); ``` metric定义 ```typescript export interface ResourceMetric { // 页面整体大小:包括主文档、所有JS、CSS、Img、Font,单位KB allSize: number; // 主文档大小 KB docSize: number; // 主文档的响应header的大小,包含cookie等 KB headerSize: number; // js外链的个数 jsNum: number; cssNum: number; imgNum: number; fontNum: number; // 所有JS外链的大小 jsSize: number; cssSize: number; imgSize: number; fontSize: number; // 页面整体网络传输大小,通常来说资源有了缓存,传输大小就为0,另外有Gzip的话,传输大小相比资源本身大小也要小很多 allTransferSize: number; // 主文档网络传输大小 docTransferSize: number; // 所有JS外链的传输大小 jsTransferSize: number; cssTransferSize: number; imgTransferSize: number; fontTransferSize: number; // js cache率 jsCacheRate: number; cssCacheRate: number; imgCacheRate: number; }; ``` hostMetric定义 ```typescript export interface ResourceHostMetric { [host: string]: { hostNum: number; hostSize: number; hostTransferSize: number; hostDuration: number; hostCacheRate: number; }; }; ``` #### 加载慢的资源 ```javascript // 数据类型:异常,触发时间:500MsAfterOnLoad spy.listenSlowResource(function (info) { spy.sendExcept({ info: info }); }, {threshold: 1000}); ``` info定义 ```typescript export interface ResourceErrorInfo { // 发生异常的资源链接 msg: string; // 发生异常的资源元素的xpath信息,一直到body xpath: string; // 资源host host: string; // 资源类型 type: string; // 资源耗时 dur?: number; } ``` 第二个参数,option定义 ```typescript export interface SlowOption { /** * 加载时长大于该阈值,就认为是慢资源,默认1000,单位是ms */ threshold?: number; /** * 忽略指定path的资源 */ ignorePaths?: string[]; /** * 触发时机,在load事件触发后,还是在用户离开页面后,收集出现的加载慢的资源。,默认是load */ trigger?: 'load' | 'leave'; } ``` #### 大于150KB的大图检测 大于150KB(默认,第二个参数可以修改)的来自img标签的大图检测。 ```javascript // 数据类型:异常,触发时间:500MsAfterOnLoad spy.listenBigImg(function (info) { spy.sendExcept({ info: info }); }); ``` info定义 ```typescript export interface ResourceErrorInfo { // 发生异常的资源链接 msg: string; // 发生异常的资源元素的xpath信息,一直到body xpath: string; // 资源host host: string; // 资源类型 type: string; // 资源耗时 dur?: number; } ``` 第二个参数,option定义 ```typescript export interface BigImgOption { /** * 体积大于该阈值,就认为是大图,默认150,单位是kb */ maxSize?: number; /** * 忽略指定path的资源 */ ignorePaths?: string[]; /** * 触发时机,在load事件触发后,还是在用户离开页面后,收集出现的加载慢的资源。,默认是load */ trigger?: 'load' | 'leave'; } ``` #### HTTPS页面里的HTTP资源检测 ```javascript // 数据类型:异常,触发时间:500MsAfterOnLoad spy.listenHttpResource(function (info) { spy.sendExcept({ info: info }); }); ``` info定义 ```typescript export interface ResourceErrorInfo { // 发生异常的资源链接 msg: string; // 发生异常的资源元素的xpath信息,一直到body xpath: string; // 资源host host: string; // 资源类型 type: string; // 资源耗时 dur?: number; } ``` 第二个参数,option定义 ```typescript export interface HttpResOption { /** * 忽略指定path的资源 */ ignorePaths?: string[]; /** * 触发时机,在load事件触发后,还是在用户离开页面后,收集出现的加载慢的资源。,默认是load */ trigger?: 'load' | 'leave'; } ``` #### T7内核首屏时间内的LongTask信息 * 依赖:spy-head.js ```javascript // 数据类型:性能,触发时间:OnLoad spy.listenFSPLongTask(function (metric) { spy.sendPerf({ info: metric }); }); ``` metric定义 ```typescript export interface FSPLongtaskMetric { // 在T7内核首屏时间内, 每个longtask的时间总和 fspLongtaskTime: number; // 在T7内核首屏时间内,Total Blocking Time时间总和,即Sum(每个longtask的时间 - 50) // Total Blocking Time定义: fspTBT: number; // T7内核首屏时间 fspTotalTime: number; // 在T7内核首屏时间内, Longtask率 = 100 * fspLongtaskTime / fspTotalTime fspLongtaskRate: number; // 在T7内核首屏时间内的Longtask数量 fspLongtaskNum: number; } ``` #### Largest Contentful Paint时间内LongTask信息 * 依赖:spy-head.js ```javascript // 数据类型:性能,触发时间:500MsAfterOnLoad spy.listenLCPLongTask(function (metric) { spy.sendPerf({ info: metric }); }); ``` metric定义 ```typescript export interface LCPLongtaskMetric { // 在Largest Contentful Paint时间内, 每个longtask的时间总和 lcpLongtaskTime: number; // 在Largest Contentful Paint时间内,Total Blocking Time时间总和,即Sum(每个longtask的时间 - 50) lcpTBT: number; // Largest Contentful Paint时间 lcpTotalTime: number; // 在Largest Contentful Paint时间内, Longtask率 = 100 * lcpLongtaskTime / lcpTotalTime lcpLongtaskRate: number; // 在Largest Contentful Paint时间内的Longtask数量 lcpLongtaskNum: number; } ``` #### 页面加载过程的LongTask信息 * 依赖:spy-head.js ```javascript // 数据类型:性能,触发时间:OnLoad spy.listenLoadLongTask(function (metric) { spy.sendPerf({ info: metric }); }); ``` metric定义 ```typescript export interface LoadLongtaskMetric { // 在onload即页面加载完成时间内, 每个longtask的时间总和 loadLongtaskTime: number; // 在onload即页面加载完成时间内,Total Blocking Time时间总和,即Sum(每个longtask的时间 - 50) loadTBT: number; // onload即页面加载完成时间 loadTotalTime: number; // 在onload即页面加载完成时间内, Longtask率 = 100 * loadLongtaskTime / loadTotalTime loadLongtaskRate: number; // 在onload即页面加载完成时间内的Longtask数量 loadLongtaskNum: number; } ``` #### 页面完整周期内的LongTask信息 开始加载页面到第一次离开页面(隐藏或点出)时间内的LongTask信息 * 依赖:spy-head.js ```javascript // 数据类型:性能,触发时间:OnLeavingPageFirstly spy.listenPageLongTask(function (metric) { spy.sendPerf({ info: metric }); }); ``` metric定义 ```typescript export interface PageLongtaskMetric { // 在开始加载页面到第一次离开页面(隐藏或点出)时间内, 每个longtask的时间总和 pageLongtaskTime: number; // 在开始加载页面到第一次离开页面(隐藏或点出)时间内,Total Blocking Time时间总和,即Sum(每个longtask的时间 - 50) pageTBT: number; // 开始加载页面到第一次离开页面(隐藏或点出)时间 pageTotalTime: number; // 在开始加载页面到第一次离开页面(隐藏或点出)时间内, Longtask率 = 100 * pageLongtaskTime / pageTotalTime pageLongtaskRate: number; // 在开始加载页面到第一次离开页面(隐藏或点出)时间内的Longtask数量 pageLongtaskNum: number; // 在开始加载页面到第一次离开页面(隐藏或点出)时间内,每个来自iframe内的longtask的时间总和 pageIframeLongtaskTime: number; // 在开始加载页面到第一次离开页面(隐藏或点出)时间内,每个来自iframe内的Longtask率 = 100 * pageIframeLongtaskTime / pageTotalTime pageIframeLongtaskRate: number; // 在开始加载页面到第一次离开页面(隐藏或点出)时间内,每个来自iframe内的Longtask数量 pageIframeLongtaskNum: number; } ``` #### Cumulative Layout Shift 页面布局的变化程度, 变化过多,可能让用户觉得页面不稳定,抖动 * 依赖:spy-head.js ```javascript // 数据类型:性能,触发时间:OnLeavingPageFirstly spy.listenLayoutShift(function (metric) { spy.sendPerf({ info: metric }); }); ``` metric定义 ```typescript export interface LayoutShiftMetric { // Cumulative Layout Shift定义 https://web.dev/cls/ // 在开始加载页面到第一次离开页面(隐藏或点出)时间内的 Cumulative Layout Shift layoutShift: number; } ``` #### 内存信息 页面使用内存信息 在页面第一次离开时(隐藏或点出)触发 ```javascript // 数据类型:性能,触发时间:在页面第一次离开时(隐藏或点出) spy.listenMemory(function (metric) { spy.sendPerf({ info: metric }); }); ``` metric定义 ```typescript export interface MemoryMetric { // 已使用内存, 单位KB usedJSHeapSize: number; // 分配给页面的内存,单位KB totalJSHeapSize: number; // 内存限制,单位KB jsHeapSizeLimit: number; // 内存使用率百分比 = 100 * usedJSHeapSize / totalJSHeapSize usedJSHeapRate: number; } ``` #### Navigator信息 获取一些设备信息 ```javascript // 数据类型:性能,触发时间:OnLoad const info = spy.getNavigatorInfo(); ``` info定义 ```typescript export interface NavigatorInfoMetric { // 网络下载速度 https://developer.mozilla.org/zh-CN/docs/Web/API/Navigator/connection downlink?: number; // 网络类型 effectiveType?: '2g' | '3g' | '4g' | 'slow-2g'; // 估算的往返时间 rtt?: number; // 数据保护模式 saveData?: boolean; // 设备内存 https://developer.mozilla.org/en-US/docs/Web/API/Navigator/deviceMemory deviceMemory?: number; // 设备逻辑核数 https://developer.mozilla.org/en-US/docs/Web/API/NavigatorConcurrentHardware/hardwareConcurrency hardwareConcurrency?: number; } ``` ## Example 样例参考 [Example](https://github.com/kaivean/spy-client/blob/master/example/index.html) ## 自定义构建 #### clone准备 如果觉得spy-client太大,只想要部分模块,比如禁用 longtask,可以拉取源码,自行编译 ``` git clone https://github.com/kaivean/spy-client.git cd spy-client npm install ``` #### 禁用模块 比如禁用longtask,前往`src/spy-client.ts` 在顶部注释掉import ```typescript // import Longtask from './module/longtask'; ``` 在constructor里注释掉register ```typescript // this.register(new Longtask()); ``` #### 构建 ```bash npm run build ``` 然后找到dist/spy-client.min.js 就是构建压缩版代码 ## 开发 ```bash # 启动本地调试页面,进行调试 npm run example # 进行watch 编译, 一般和上个命令配合使用 npm run watch # lint npm run lint # 测试 npm run test # production编译,产出到dist npm run build # development编译,产出到dist npm run dev # 发布 # 1. 构建测试 npm run release_pre # 2. 提交代码 git add . && git commit -m "升级/Fix" # 3. 发布npm包,增加tag npm run release # 4. 修改Readme文档里版本号 git add . && git commit -m "修改文档版本" # 5. 把代码push到远程 npm run release_post ```