项目:https://github.com/latel/logline,欢迎关注和 star 。
从事 Web 前端同学对此肯定深有体会,代码发出去之后,犹如脱缰的野马,运行在万千的客户终端上,等到产品和后台反馈问题到我们这边,很多时候定位问题只能靠猜,尤其是一些偶发诱因,因为根本不知道用户是如何操作的,真实环境遇到的问题通常是很多随机因素叠加的形成的,因此很难回放用户的操作来还原现场找到原因。
经过一段时间经验的累积,我们想到,如果我们有一个同后台一样详实的可分类和检索的代码运行日志,无疑将会提供巨大的帮助。
一个健壮的日志系统大致包含日志记录、日志上传和日志分析三个主要部分,在这次的实践中,我们对这三方面都有所探索。
由于前端受到很多限制,不能同 APP 一样可以在文件沙箱内存取文件,因此如何持久化的存储日志成了一个问题。目前 H5 也支持若干种本地存储方案, cookie, localStorage, indexedDB 和 websql 等,其他的由插件提供的能力不在考虑的范围之类,如 flash ,移动端的可用性会是一个很大的问题。
不用想,应该没有人会考虑吧, Cookie 的原则应该是尽可能的精简避免浪费带宽。
localStorage 大家应该都很熟悉,一个简单的键值存储系统,接口简单实用,兼容性也非常的棒。但是考虑到目前很多项目都有使用 localStorage 来做缓存,而 localStorage 本身是有大小限制的。根据日志记录的细粒度,很可能会产生较多的日志内容,如果也记录在 localStorage 里,可能会有超出容量限制的风险。因此, localStorage 应该作为一个备用的持久化方案。
websql 作为一项 W3C 标准,目前已经被废弃,但是各大桌面浏览器和移动端浏览器都有很好的实现这个接口,兼容性问题不大,底层基本上都是 sqlite (正是因为这样,作为一个 web 标准是不可接受的)。因而日志作为大量的结构化数据,应用场景非常的适合。经测试,在 iOS 上容量最大支持 50MB ,不过如果使用系统自带的 safari ,超过 5MB 时,会主动提醒用户是否要增加数据库的大小,不是很友好,不过微信里到是不会。想想 5MB 独占作为日志存储基本上够用了,处理好过旧日志的清理工作即可。因此我们觉得 websql 应当作为当前阶段主要的解决方案。
IndexedDB 是一种可以让你在用户的浏览器内持久化存储数据的方法,作为下一代的客户端结构化数据持久存储方案,足够的强大和高效,目前在各大浏览器中也有很好的支持,是未来用来替换 websql 的方案,应当在日后的迭代中主要支持,用来替代 websql 方案。
info
, warning
, error
, critical
等,以提供最为基本的过滤能力。verify.request.start
, submit.prevented
等等。尽可能的以便用户一眼就可以知晓日志的大致内容,也方便代码中日志记录代码的可读性,也更易于在代码中搜索。由于我们并不需要实时的获取来自客户端的大量日志,因此日志是存储在用户客户端本地的,我们需要后台配合开发一个用于接收客户端通过网络上传的日志内容并存储起来的接口。同时可能要考虑一些安全性问题,如引入 token 机制和验证登录态等等。目前我们腾讯微证券采用的方案为:用户在微信公众号中发送消息“问题反馈”(直接使用日志上报等关键词可能会引起用户的反感),后台会返回日志上传页面地址链接,用户点击链接进入后,在获取登录态后自动上报日志。大家可以微信关注腾讯微证券
公众号尝试一下。
由于 Logline 上传的日志格式符合标准,具有良好的可阅读性,因此我们可以在某种程度上直接使用命令行工具或者编辑器来阅读。但是对命令行不熟悉的用户使用可能仍然有困难,因此有必要使用 Web 技术栈搭建一个易于使用并且视觉良好的工具。我们希望这套工具可以不依赖与后端,既可以部署在服务器端,也可以当做本地网页直接双击打开,也可以被简单的包一层外壳而当做桌面 APP 来使用。
作为日志,承载的最主要的内容便是大量的纯文本,在调研了一些方案后,我们认为 H5 规范中的 FileReader.readAsText 可以很好的做到这一点,结合拖放事件,我们便可以很大致构建出一个不错的方案:用户将一个或者多个日志文件拖放至网页中,即可对这些日志批量分析和检索。
我们构建了一个仅供体验的版本 logline-viewer。
基于以上工作,我们腾讯微证券项目组推出了自己的解决方案:Logline
,一个轻量,实用和客户端级的前端日志记录工具。
http://kezhen.info/logline/example/
回放用户细节操作
真实应用场景下,用户的行为可能是不可预料的,甚至用户自己也无法记得自己的操作,有了日志,我们有了回放用户操作和代码运行状态的能力。
核心流程监控
在产品的一些核心流程中,我们可以在用户出错的情况下主动上传用户日志,以便我们可以快速统计和定位用户遇到的问题。
主动抓取用户的日志分析用户行为
有时候在用户不配合开发人员的时候,我们可以设计一种策略,比如我们在线上发布一个 json 文件,里面配置一个希望主动抓取日志的用户列表,当我们的产品在用户手机上被打开后,延时下载(避免影响主流程性能)这个 json ,当匹配当前用户时,直接主动上报该用户的日志。
统计和辅助分析 JS 错误
我们可以记录 js 的报错,包含调用队列一起记录,直接上传此错误日志或者在累计达到一个阈值的时候统一上传。
bower install logline
访问 https://github.com/latel/logline/releases,选择需要的版本下载,引入自己的项目。
Logline 支持直接使用 script 标签引用,也支持 AMD 模块加载器.
// Script 标签引入方式
<script src="./mod/logline.min.js"></script>
<script>
// 使用 indexedDB 协议
Logline.using(Logline.PROTOCOL.INDEXEDDB);
</script>
// AMD 模块方式
var Logline = require('./mod/logline.min');
目前一共支持三个协议, 三个协议都被直接挂载在 Logline 对象上以便一些特殊的应用场景,也更好的符合语义化:
你可以在引入 Logline 之后,使用 using
主动选定一个期望使用的日志协议。
Logline.using(Logline.PROTOCOL.WEBSQL);
如果你没有提前选择一个日志协议,那么当你调用 Logline 的相关 API 时, Logline 会根据你在构建时给定的参数作为优先级来选择可用的优先级最高的协议。
比如你的自定义构建命令是npm run configure -- --with-indexeddb --with-websql --with-localstorage
,
如果 indexeddb 协议可用,那么 indexeddb 将作为自动选择的协议。
如果 indexeddb 协议不可用但是 websql 协议可用,那么将选择 websql 协议,如此类推。
如果最后发现所有的协议都不可用,将会抛出错误。
// 不同的模块使用不同的日志会话
var spaLog = new Logline('spa'),
sdkLog = new Logline('sdk');
// 不包含数据的,描述为 init.succeed 的记录
spaLog.info('init.succeed');
// 包含错误描述数据,描述为 init.failed 的记录
spaLog.error('init.failed', {
retcode: 'EINIT',
retmsg: 'invalid signature'
});
// 不包含数据的,描述为 outdated 的记录
sdkLog.warning('outdated');
// 包含错误描述数据,描述为 system.vanish 的记录
sdkLog.critical('system.vanish', {
// debug infos here
});
Logline.getAll(function(logs) {
// process logs here
});
Logline.keep(.5); // 保留半天以内的日志,如果不传参则清空日志
Logline.clean(); // 清空日志并删除数据库
由于 indexeddb, websql 和 localStorage 都是同域共享的,这时候 Logline 默认的数据库名 logline 可能会已经被占用,需要指定一个新的数据库名。 可以通过下面 2 个方法指定数据库名。
// 调用`using`时,同时指定第二个参数作为数据库名
Logline.using(Logline.PROTOCOL.WEBSQL, 'newlogline');
// 调用`database`来指定数据库名
Logline.database('newlogline');
目前 Logline 一共实现了localstorage
、websql
和indexeddb
三个日志协议,默认是全部打包,可能你只想使用其中某个协议而已,你可以通过npm run configure
来自定义构建你需要的版本。这样有利于减小包的大小。
// 不跟参数默认构建所有协议
npm run configure
// 配置你需要的协议,去掉不需要的协议申明--with-xxx
npm run configure -- --with-localstorage --with-websql --with-indexeddb
// 重新打包
npm run build
// 去 dist 目录寻找新构建的打包文件
开户流程作为核心的流程之一,一直以来都有着比较完善的监控数据,在某次改版之后,我们发现用户开户提交失败的比例有着显著的上升,很多用户在最终提交开户资料时,后端校验发现某些用户资料尚未被设置这种情况很让我们困惑。也是促使我们开发这个工具原因。在部署了 Logline 这套方案后,我们在开户流程的代码中添加了较为详实的日志记录,并在出错的情况下主动上报日志到后端。发现是在某些机型中,用户点击下一步后,全局的 loading 遮罩显示较慢,导致用户快速连续 2 次点击了下一步,导致开户流程控制器连续 2 次触发下一步操作,使得某些页面被跳过导致。
目前和财付通很多前端产品一样,我们接入了财付通统一日志上报平台,但是我们上传的内容很有限,访问性能也不是很好,也没法看到 js 错误的调用队列,甚至没法看到用户的 useragent 。在使用我们自己的 Logline 上报系统后,这一切都得到了解决,发现了一些由微信 jssdk 自身产生的错误如 'WeixinJSBridge is not defined'和一些上报量比较大的 js 错误,有效的保证了代码的质量,甚至还可以做一个错误实时曲线统计。
由于产品正处于快速的功能迭代中,目前不论是业务代码还是核心模块的代码都会经常被改动,不可避免的会产生一些意想不到的问题。有时候,我们会去找到用户希望用户配合帮忙定位问题,但是并不是所有的用户都会愿意配合。于是我们设计了一套主动抓取用户日志的策略:在我们的产品页面被打开后,会延时一定的时间去下载一个 logcat.json 的文件,文件中会包含我们期望可以主动上报日志的用户列表,当和用户吻合时,该用户的日志将会被自动上传。在需要的时候,我们只需要维护这样一个配置文件即可,当配置中的用户日志被上传后,我们将会受到一封邮件提醒。也可以使用此配置文件来配置客户端 JS 是否要自动上报 js 错误。
我们相信前端的社区应该是开放和合作的。
[ ] 不使用 webpack 编译,以减小打包文件的大小 [√i] 加入 bower 包管理器 [ ] 丰富英文代码注释 [ ] 完善错误处理细节 [ ] 测试环境由 jsdom 迁移至 phantomjs [ ] 优化存取结构
在这次的实践中,我们有意保持模块的独立性,并没有使用任何外部依赖,因此很容易复用到别的业务或者绑定自己的业务逻辑。不过在未来,我们更喜欢 Logline 作为前端日志的一整套解决方案提供,而无需业务在上传和分析部分要做出自己的实现。
更为可用的应用场景应该是业务放在我们的平台上申请一个 appid ,然后使用我们的统计脚本,所以日志将统一上报到我们的平台,业务方可以使用此 appid 在我们的系统中浏览、分析和检索所需的日志。
本方案使用的技术和实现难度并不困难,重要的是一种解决痛点的思路。
1
Laygle 2016-12-08 01:14:28 +08:00
很强大,解决了很多人的困扰啊,感谢分享。
|
2
peneazy 2016-12-08 06:44:45 +08:00 via Android
mark
|
3
wensonsmith 2016-12-08 09:11:56 +08:00
不错,正需要用到,研究下
|
4
jsq2627 2016-12-08 10:36:21 +08:00 via iPhone
mark
|