[TOC]
# 收集用户信息
## navigator API
* 当前的网络状态
* 运营商
* 地理位置
* 访问时间
* 客户端的版本(如果是通过客户端访问)
* 系统版本
* 浏览器信息
* 设备分辨率
* 页面的来源
* 用户账号信息
* 页面访问流程各阶段耗时
* js代码报错信息
* 用户的浏览器语言编码
* 浏览器语言设置
* flash的版本
* 网页的标题
* 网页的来源
* cookie的数据
<br>
# 各阶段访问耗时记录
## performance timing
[performance API](https://developer.mozilla.org/en-US/docs/Web/API/Performance)
* DNS 查询耗时 :domainLookupEnd - domainLookupStart
* TCP 链接耗时 :connectEnd - connectStart
* request 请求耗时 :responseEnd - responseStart
* 解析 dom 树耗时 : domComplete - domInteractive
* 白屏时间 :responseStart - navigationStart
* domready 时间 :domContentLoadedEventEnd - navigationStart
* onload 时间 :loadEventEnd – navigationStart
具体参见[W3C Resource timing](https://www.w3.org/TR/resource-timing/),[Performance.getEntries()](https://developer.mozilla.org/zh-CN/docs/Web/API/Performance/getEntries)
## performance getEntries
通过performance.getEntries(),可以得到一个数组,其中的每个元素分别代表着一个资源,元素对象包括的属性跟上面的performance timing有点接近,还有不同的属性包括name代表资源的地址,请求花费的时间duration。
<br>
## 其他方法
记录访问开始的时间可有以下的方法:
* 服务器将访问的时间渲染到页面上
* SPA的话,记录前一个页面卸载的时间
记录访问过程的时间
* 在head标签解析后,渲染body标签前加入script标签进行打点,一般将这个时间视为白屏时间
* 捕获DOMContentLoaded事件来记录dom元素加载完毕的时间
* 在首屏页面的所有图片加载完后进行记录,保存首屏时间
* 捕获load事件记录页面加载完成的时间
<br>
<br>
# 错误采集
## window.onerror
window.onerror可以捕捉运行时错误,可以拿到出错的信息,堆栈,出错的文件、行号、列号
~~~
window.onerror = function(msg, url, row, col, error){
report({
msg, // 错误信息
url, // 发生错误对应的脚本链接
row, // 行号
col // 列号
})
}
~~~
> 注: onerror兼容性有问题,ie10 & safari6 才完整支持以上全部参数。
>
要注意以下几点:
* 要把 `window.onerror` 这个代码块分离出去,并且比其他脚本先执行(注意这个前提!)即可捕捉到语法错误。
* 由于网络请求异常事件不会冒泡,需要在**捕获**阶段进行处理
* 不能捕获 **promise** 的错误信息
* 跨域资源需要专门处理,需要在script标签加上 **crossorigin** 属性,服务器设置 `Access-Control-Allow-Origin`
* window.onerror 函数只有在返回 true 的时候,异常才不会向上抛出,否则即使是知道异常的发生控制台还是会显示 Uncaught Error: xxxxx。
<br>
## 跨域资源处理
Script error 是浏览器在同源策略限制下产生的,浏览器处于对安全性上的考虑,当页面引用非同域名外部脚本文件时中抛出异常的话,此时本页面是没有权利知道这个报错信息的,取而代之的是输出 Script error 这样的信息。
首先为页面上的 script 标签添加 crossOrigin 属性
~~~html
<script src="http://localhost:8081/test.js" crossorigin></script>
~~~
其次,后端在响应头里加上
~~~
Access-Control-Allow-Origin: *
~~~
<br>
## promise的错误处理
通过 Promise 可以帮助我们解决异步回调地狱的问题,但是一旦 Promise 实例抛出异常而你没有用 catch 去捕获的话,onerror 或 try-catch 也无能为力,无法捕捉到错误。
~~~js
window.addEventListener('error', (msg, url, row, col, error) => {
console.log('我感知不到 promise 错误');
console.log(
msg, url, row, col, error
);
}, true);
Promise.reject('promise error');
new Promise((resolve, reject) => {
reject('promise error');
});
new Promise((resolve) => {
resolve();
}).then(() => {
throw 'promise error'
});
~~~
所以如果你的应用用到很多的 Promise 实例的话,特别是你在一些基于 promise 的异步库比如 axios 等一定要小心,因为你不知道什么时候这些异步请求会抛出异常而你并没有处理它,所以你最好添加一个 Promise 全局异常捕获事件 `unhandledrejection`。
~~~
window.addEventListener("unhandledrejection", function(e){
// Event新增属性
// @prop {Promise} promise - 状态为rejected的Promise实例
// @prop {String|Object} reason - 异常信息或rejected的内容
// 会阻止异常继续抛出,不让Uncaught(in promise) Error产生
e.preventDefault()
})
~~~
<br>
## try catch
无法捕捉到语法错误,只能捕捉运行时错误;
可以拿到出错的信息,堆栈,出错的文件、行号、列号; 需要借助工具把所有的function块以及文件块加入try,catch,可以在这个阶段打入更多的静态信息。
要注意的是try catch只能捕获同步代码的异常,对回调,setTimeout,promise等无能为力
<br>
## 网络异常错误
由于网络请求异常不会事件冒泡,因此必须在捕获阶段将其捕捉到才行,但是这种方式虽然可以捕捉到网络请求的异常,但是无法判断 HTTP 的状态是 404 还是其他比如 500 等等,所以还需要配合服务端日志才进行排查分析才可以。
~~~
<img src="./404.png" alt="">
<script>
window.addEventListener('error', (msg, url, row, col, error) => {
console.log('我知道 404 错误了');
console.log(
msg, url, row, col, error
);
return true;
}, true);
</script>
~~~
<br>
<br>
# 上报错误
* 后端提供接口,前端ajax上传
* 创建一个新的图片,url参数带上错误信息
使用创建图片的好处是简单方便可跨域,可以防止重复请求,但要注意到地址栏可以携带的信息有限。
~~~
function report(error) {
var reportUrl = 'http://xxxx/report';
new Image().src = reportUrl + 'error=' + error;
}
~~~
### 页面加载时间统计
~~~
window.logInfo = {};
window.logInfo.openTime = performance.timing.navigationStart;
// 白屏时间
window.logInfo.whiteScreenTime = +new Date() - window.logInfo.openTime;
document.addEventListener('DOMContentLoaded', function (event) {
window.logInfo.readyTime = +new Date() - window.logInfo.openTime;
});
window.onload = function () {
window.logInfo.allloadTime = +new Date() - window.logInfo.openTime;
window.logInfo.nowTime = new Date().getTime();
var timname = {
whiteScreenTime: '白屏时间',
readyTime: '用户可操作时间',
allloadTime: '总下载时间',
mobile: '使用设备',
nowTime: '时间',
};
var logStr = '';
for (var i in timname) {
console.warn(timname[i] + ':' + window.logInfo[i] + 'ms');
if (i === 'mobile') {
logStr += '&' + i + '=' + window.logInfo[i];
} else {
logStr += '&' + i + '=' + window.logInfo[i];
}
}
(new Image()).src = '/action?' + logStr;
};
~~~
<br>
### 统计用户使用设备
~~~
window.logInfo.mobile = mobileType();
function mobileType() {
var u = navigator.userAgent, app = navigator.appVersion;
var type = {// 移动终端浏览器版本信息
ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), //ios终端
iPad: u.indexOf('iPad') > -1, //是否iPad
android: u.indexOf('Android') > -1 || u.indexOf('Linux') > -1, //android终端或者uc浏览器
iPhone: u.indexOf('iPhone') > -1 || u.indexOf('Mac') > -1, //是否为iPhone或者QQHD浏览器
trident: u.indexOf('Trident') > -1, //IE内核
presto: u.indexOf('Presto') > -1, //opera内核
webKit: u.indexOf('AppleWebKit') > -1, //苹果、谷歌内核
gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') == -1, //火狐内核
mobile: !!u.match(/AppleWebKit.*Mobile/i) || !!u.match(/MIDP|SymbianOS|NOKIA|SAMSUNG|LG|NEC|TCL|Alcatel|BIRD|DBTEL|Dopod|PHILIPS|HAIER|LENOVO|MOT-|Nokia|SonyEricsson|SIE-|Amoi|ZTE/), //是否为移动终端
webApp: u.indexOf('Safari') == -1 //是否web应该程序,没有头部与底部
};
var lists = Object.keys(type);
for(var i = 0; i < lists.length; i++) {
if(type[lists[i]]) {
return lists[i];
}
}
}
~~~
<br>
### 错误量的统计
~~~
var defaults = {
msg: '', // 错误的具体信息
url: '', // 错误所在的url
line: '', // 错误所在的行
col: '', // 错误所在的列
nowTime: '',// 时间
};
window.onerror = function (msg, url, line, col, error) {
col = col || (window.event && window.event.errorCharacter) || 0;
defaults.url = url;
defaults.line = line;
defaults.col = col;
defaults.nowTime = new Date().getTime();
if (error && error.stack) {
// 如果浏览器有堆栈信息,直接使用
defaults.msg = error.stack.toString();
} else if (arguments.callee) {
// 尝试通过callee拿堆栈信息
var ext = [];
var fn = arguments.callee.caller;
var floor = 3;
while (fn && (--floor > 0)) {
ext.push(fn.toString());
if (fn === fn.caller) {
break;
}
fn = fn.caller;
}
ext = ext.join(",");
defaults.msg = error.stack.toString();
}
var str = ''
for (var i in defaults) {
// console.log(i,defaults[i]);
if (defaults[i] === null || defaults[i] === undefined) {
defaults[i] = 'null';
}
str += '&' + i + '=' + defaults[i].toString();
}
srt = str.replace('&', '').replace('\n', '').replace(/\s/g, '');
(new Image()).src = '/error?' + srt;
}
~~~
### 数据处理、数据展示
# 工具、平台
## [Fundebug](https://www.fundebug.com/)
## [AlloyLever](https://github.com/AlloyTeam/AlloyLever)
<br>
# 参考资料
[搭建一个前端监控系统,不再错过BUG - 掘金](https://juejin.im/post/5a372716518825258a5fbc80)
[前端错误监控与收集探究 | hpoenixf's blog](http://hpoenixf.com/前端错误监控与收集探究.html)
https://juejin.im/post/5b35921af265da598f1563cf
https://juejin.im/post/5aaa93345188257bf550cbfd
https://juejin.im/post/5b5dcfb46fb9a04f8f37afbb
[前端代码异常监控实战](https://zhuanlan.zhihu.com/p/31979395)
https://zhuanlan.zhihu.com/p/27305665
- 第一部分 HTML
- meta
- meta标签
- HTML5
- 2.1 语义
- 2.2 通信
- 2.3 离线&存储
- 2.4 多媒体
- 2.5 3D,图像&效果
- 2.6 性能&集成
- 2.7 设备访问
- SEO
- Canvas
- 压缩图片
- 制作圆角矩形
- 全局属性
- 第二部分 CSS
- CSS原理
- 层叠上下文(stacking context)
- 外边距合并
- 块状格式化上下文(BFC)
- 盒模型
- important
- 样式继承
- 层叠
- 属性值处理流程
- 分辨率
- 视口
- CSS API
- grid(未完成)
- flex
- 选择器
- 3D
- Matrix
- AT规则
- line-height 和 vertical-align
- CSS技术
- 居中
- 响应式布局
- 兼容性
- 移动端适配方案
- CSS应用
- CSS Modules(未完成)
- 分层
- 面向对象CSS(未完成)
- 布局
- 三列布局
- 单列等宽,其他多列自适应均匀
- 多列等高
- 圣杯布局
- 双飞翼布局
- 瀑布流
- 1px问题
- 适配iPhoneX
- 横屏适配
- 图片模糊问题
- stylelint
- 第三部分 JavaScript
- JavaScript原理
- 内存空间
- 作用域
- 执行上下文栈
- 变量对象
- 作用域链
- this
- 类型转换
- 闭包(未完成)
- 原型、面向对象
- class和extend
- 继承
- new
- DOM
- Event Loop
- 垃圾回收机制
- 内存泄漏
- 数值存储
- 连等赋值
- 基本类型
- 堆栈溢出
- JavaScriptAPI
- document.referrer
- Promise(未完成)
- Object.create
- 遍历对象属性
- 宽度、高度
- performance
- 位运算
- tostring( ) 与 valueOf( )方法
- JavaScript技术
- 错误
- 异常处理
- 存储
- Cookie与Session
- ES6(未完成)
- Babel转码
- let和const命令
- 变量的解构赋值
- 字符串的扩展
- 正则的扩展
- 数值的扩展
- 数组的扩展
- 函数的扩展
- 对象的扩展
- Symbol
- Set 和 Map 数据结构
- proxy
- Reflect
- module
- AJAX
- ES5
- 严格模式
- JSON
- 数组方法
- 对象方法
- 函数方法
- 服务端推送(未完成)
- JavaScript应用
- 复杂判断
- 3D 全景图
- 重载
- 上传(未完成)
- 上传方式
- 文件格式
- 渲染大量数据
- 图片裁剪
- 斐波那契数列
- 编码
- 数组去重
- 浅拷贝、深拷贝
- instanceof
- 模拟 new
- 防抖
- 节流
- 数组扁平化
- sleep函数
- 模拟bind
- 柯里化
- 零碎知识点
- 第四部分 进阶
- 计算机原理
- 数据结构(未完成)
- 算法(未完成)
- 排序算法
- 冒泡排序
- 选择排序
- 插入排序
- 快速排序
- 搜索算法
- 动态规划
- 二叉树
- 浏览器
- 浏览器结构
- 浏览器工作原理
- HTML解析
- CSS解析
- 渲染树构建
- 布局(Layout)
- 渲染
- 浏览器输入 URL 后发生了什么
- 跨域
- 缓存机制
- reflow(回流)和repaint(重绘)
- 渲染层合并
- 编译(未完成)
- Babel
- 设计模式(未完成)
- 函数式编程(未完成)
- 正则表达式(未完成)
- 性能
- 性能分析
- 性能指标
- 首屏加载
- 优化
- 浏览器层面
- HTTP层面
- 代码层面
- 构建层面
- 移动端首屏优化
- 服务器层面
- bigpipe
- 构建工具
- Gulp
- webpack
- Webpack概念
- Webpack工具
- Webpack优化
- Webpack原理
- 实现loader
- 实现plugin
- tapable
- Webpack打包后代码
- rollup.js
- parcel
- 模块化
- ESM
- 安全
- XSS
- CSRF
- 点击劫持
- 中间人攻击
- 密码存储
- 测试(未完成)
- 单元测试
- E2E测试
- 框架测试
- 样式回归测试
- 异步测试
- 自动化测试
- PWA
- PWA官网
- web app manifest
- service worker
- app install banners
- 调试PWA
- PWA教程
- 框架
- MVVM原理
- Vue
- Vue 饿了么整理
- 样式
- 技巧
- Vue音乐播放器
- Vue源码
- Virtual Dom
- computed原理
- 数组绑定原理
- 双向绑定
- nextTick
- keep-alive
- 导航守卫
- 组件通信
- React
- Diff 算法
- Fiber 原理
- batchUpdate
- React 生命周期
- Redux
- 动画(未完成)
- 异常监控、收集(未完成)
- 数据采集
- Sentry
- 贝塞尔曲线
- 视频
- 服务端渲染
- 服务端渲染的利与弊
- Vue SSR
- React SSR
- 客户端
- 离线包
- 第五部分 网络
- 五层协议
- TCP
- UDP
- HTTP
- 方法
- 首部
- 状态码
- 持久连接
- TLS
- content-type
- Redirect
- CSP
- 请求流程
- HTTP/2 及 HTTP/3
- CDN
- DNS
- HTTPDNS
- 第六部分 服务端
- Linux
- Linux命令
- 权限
- XAMPP
- Node.js
- 安装
- Node模块化
- 设置环境变量
- Node的event loop
- 进程
- 全局对象
- 异步IO与事件驱动
- 文件系统
- Node错误处理
- koa
- koa-compose
- koa-router
- Nginx
- Nginx配置文件
- 代理服务
- 负载均衡
- 获取用户IP
- 解决跨域
- 适配PC与移动环境
- 简单的访问限制
- 页面内容修改
- 图片处理
- 合并请求
- PM2
- MongoDB
- MySQL
- 常用MySql命令
- 自动化(未完成)
- docker
- 创建CLI
- 持续集成
- 持续交付
- 持续部署
- Jenkins
- 部署与发布
- 远程登录服务器
- 增强服务器安全等级
- 搭建 Nodejs 生产环境
- 配置 Nginx 实现反向代理
- 管理域名解析
- 配置 PM2 一键部署
- 发布上线
- 部署HTTPS
- Node 应用
- 爬虫(未完成)
- 例子
- 反爬虫
- 中间件
- body-parser
- connect-redis
- cookie-parser
- cors
- csurf
- express-session
- helmet
- ioredis
- log4js(未完成)
- uuid
- errorhandler
- nodeclub源码
- app.js
- config.js
- 消息队列
- RPC
- 性能优化
- 第七部分 总结
- Web服务器
- 目录结构
- 依赖
- 功能
- 代码片段
- 整理
- 知识清单、博客
- 项目、组件、库
- Node代码
- 面试必考
- 91算法
- 第八部分 工作代码总结
- 样式代码
- 框架代码
- 组件代码
- 功能代码
- 通用代码