撸了一个前端的日志工具 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a Javascript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
Javascript 权威指南第 5 版
Closure: The Definitive Guide
latelx
V2EX    Javascript

撸了一个前端的日志工具

  •  
  •   latelx 2016-12-08 01:07:42 +08:00 4643 次点击
    这是一个创建于 3235 天前的主题,其中的信息可能已经有所发展或是发生改变。

    项目:https://github.com/latel/logline,欢迎关注和 star 。

    从事 Web 前端同学对此肯定深有体会,代码发出去之后,犹如脱缰的野马,运行在万千的客户终端上,等到产品和后台反馈问题到我们这边,很多时候定位问题只能靠猜,尤其是一些偶发诱因,因为根本不知道用户是如何操作的,真实环境遇到的问题通常是很多随机因素叠加的形成的,因此很难回放用户的操作来还原现场找到原因。

    现在的痛点

    • 用户反馈问题后,虽然我们查询了 js 报错日志和 cgi 调用,但是并没有发现任何错误,我们根本不知道用户的终端上到底发生了什么。
    • 目前使用的 js 错误上报系统,看到上报有一定的延时,作为一款金融产品,分秒必争,这短暂的延时可能是致命的,我们必须快速的找到原因并予以修复。

    经过一段时间经验的累积,我们想到,如果我们有一个同后台一样详实的可分类和检索的代码运行日志,无疑将会提供巨大的帮助。

    我们的探索

    一个健壮的日志系统大致包含日志记录、日志上传和日志分析三个主要部分,在这次的实践中,我们对这三方面都有所探索。

    日志到底存哪里?

    由于前端受到很多限制,不能同 APP 一样可以在文件沙箱内存取文件,因此如何持久化的存储日志成了一个问题。目前 H5 也支持若干种本地存储方案, cookie, localStorage, indexedDB 和 websql 等,其他的由插件提供的能力不在考虑的范围之类,如 flash ,移动端的可用性会是一个很大的问题。

    • Cookie

      不用想,应该没有人会考虑吧, Cookie 的原则应该是尽可能的精简避免浪费带宽。

    • localStorage

      localStorage 大家应该都很熟悉,一个简单的键值存储系统,接口简单实用,兼容性也非常的棒。但是考虑到目前很多项目都有使用 localStorage 来做缓存,而 localStorage 本身是有大小限制的。根据日志记录的细粒度,很可能会产生较多的日志内容,如果也记录在 localStorage 里,可能会有超出容量限制的风险。因此, localStorage 应该作为一个备用的持久化方案。

    • websql

      websql 作为一项 W3C 标准,目前已经被废弃,但是各大桌面浏览器和移动端浏览器都有很好的实现这个接口,兼容性问题不大,底层基本上都是 sqlite (正是因为这样,作为一个 web 标准是不可接受的)。因而日志作为大量的结构化数据,应用场景非常的适合。经测试,在 iOS 上容量最大支持 50MB ,不过如果使用系统自带的 safari ,超过 5MB 时,会主动提醒用户是否要增加数据库的大小,不是很友好,不过微信里到是不会。想想 5MB 独占作为日志存储基本上够用了,处理好过旧日志的清理工作即可。因此我们觉得 websql 应当作为当前阶段主要的解决方案。

    • indexedDB

      IndexedDB 是一种可以让你在用户的浏览器内持久化存储数据的方法,作为下一代的客户端结构化数据持久存储方案,足够的强大和高效,目前在各大浏览器中也有很好的支持,是未来用来替换 websql 的方案,应当在日后的迭代中主要支持,用来替代 websql 方案。

    什么样的日志格式堪称优雅?

    • 时间戳,这个无需多说,时间戳是日志的基本要义。
    • 多会话,一个前端工程中可能同时存在多个独立的模块,这些模块很可能会同时互不干扰的记录各自的日志。如果每个模块都在自己的会话下记录日志,就不会相互干扰,并且提供了另一个维度的过滤能力。
    • 错误等级,如 info, warning, error, critical 等,以提供最为基本的过滤能力。
    • 描述符,如 verify.request.start, submit.prevented 等等。尽可能的以便用户一眼就可以知晓日志的大致内容,也方便代码中日志记录代码的可读性,也更易于在代码中搜索。
    • 数据和详情,有时候一个简单的描述符并不能说明问题,可能需要记录更多的描述性文本或者数据来分析问题。

    日志怎么获取?

    由于我们并不需要实时的获取来自客户端的大量日志,因此日志是存储在用户客户端本地的,我们需要后台配合开发一个用于接收客户端通过网络上传的日志内容并存储起来的接口。同时可能要考虑一些安全性问题,如引入 token 机制和验证登录态等等。目前我们腾讯微证券采用的方案为:用户在微信公众号中发送消息“问题反馈”(直接使用日志上报等关键词可能会引起用户的反感),后台会返回日志上传页面地址链接,用户点击链接进入后,在获取登录态后自动上报日志。大家可以微信关注腾讯微证券公众号尝试一下。

    日志如何分析?

    由于 Logline 上传的日志格式符合标准,具有良好的可阅读性,因此我们可以在某种程度上直接使用命令行工具或者编辑器来阅读。但是对命令行不熟悉的用户使用可能仍然有困难,因此有必要使用 Web 技术栈搭建一个易于使用并且视觉良好的工具。我们希望这套工具可以不依赖与后端,既可以部署在服务器端,也可以当做本地网页直接双击打开,也可以被简单的包一层外壳而当做桌面 APP 来使用。

    作为日志,承载的最主要的内容便是大量的纯文本,在调研了一些方案后,我们认为 H5 规范中的 FileReader.readAsText 可以很好的做到这一点,结合拖放事件,我们便可以很大致构建出一个不错的方案:用户将一个或者多个日志文件拖放至网页中,即可对这些日志批量分析和检索。

    我们构建了一个仅供体验的版本 logline-viewer

    Logline

    基于以上工作,我们腾讯微证券项目组推出了自己的解决方案:Logline,一个轻量,实用和客户端级的前端日志记录工具。

    DEMO

    http://kezhen.info/logline/example/

    应用场景

    • 回放用户细节操作

      真实应用场景下,用户的行为可能是不可预料的,甚至用户自己也无法记得自己的操作,有了日志,我们有了回放用户操作和代码运行状态的能力。

    • 核心流程监控

      在产品的一些核心流程中,我们可以在用户出错的情况下主动上传用户日志,以便我们可以快速统计和定位用户遇到的问题。

    • 主动抓取用户的日志分析用户行为

      有时候在用户不配合开发人员的时候,我们可以设计一种策略,比如我们在线上发布一个 json 文件,里面配置一个希望主动抓取日志的用户列表,当我们的产品在用户手机上被打开后,延时下载(避免影响主流程性能)这个 json ,当匹配当前用户时,直接主动上报该用户的日志。

    • 统计和辅助分析 JS 错误

      我们可以记录 js 的报错,包含调用队列一起记录,直接上传此错误日志或者在累计达到一个阈值的时候统一上传。

    特性支持

    • 无外部依赖
    • 日志记录
    • 客户端级
    • websql/localstorage/indexeddb 三种日志协议
    • 命名空间
    • 日志等级
    • 日志清理(防止日志过多,占用上传带宽和占满用户允许的内存)

    快速上手

    1. 安装

    通过 bower

    bower install logline 

    直接下载

    访问 https://github.com/latel/logline/releases,选择需要的版本下载,引入自己的项目。

    2. 引入脚本

    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'); 

    3. 选择日志协议

    目前一共支持三个协议, 三个协议都被直接挂载在 Logline 对象上以便一些特殊的应用场景,也更好的符合语义化:

    • websql: Logline.PROTOCOL.WEBSQL
    • indexeddb: Logline.PROTOCOL.INDEXEDDB
    • localstorage: Logline.PROTOCOL.LOCALSTORAGE

    你可以在引入 Logline 之后,使用 using 主动选定一个期望使用的日志协议。

    Logline.using(Logline.PROTOCOL.WEBSQL); 

    如果你没有提前选择一个日志协议,那么当你调用 Logline 的相关 API 时, Logline 会根据你在构建时给定的参数作为优先级来选择可用的优先级最高的协议。 比如你的自定义构建命令是npm run configure -- --with-indexeddb --with-websql --with-localstorage, 如果 indexeddb 协议可用,那么 indexeddb 将作为自动选择的协议。 如果 indexeddb 协议不可用但是 websql 协议可用,那么将选择 websql 协议,如此类推。 如果最后发现所有的协议都不可用,将会抛出错误。

    4. 记录日志

    // 不同的模块使用不同的日志会话 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 }); 

    5. 读取日志

    Logline.getAll(function(logs) { // process logs here }); 

    6. 清理日志

    Logline.keep(.5); // 保留半天以内的日志,如果不传参则清空日志 Logline.clean(); // 清空日志并删除数据库 

    7. 自定义数据库名

    由于 indexeddb, websql 和 localStorage 都是同域共享的,这时候 Logline 默认的数据库名 logline 可能会已经被占用,需要指定一个新的数据库名。 可以通过下面 2 个方法指定数据库名。

    // 调用`using`时,同时指定第二个参数作为数据库名 Logline.using(Logline.PROTOCOL.WEBSQL, 'newlogline'); // 调用`database`来指定数据库名 Logline.database('newlogline'); 

    自定义构建

    目前 Logline 一共实现了localstoragewebsqlindexeddb三个日志协议,默认是全部打包,可能你只想使用其中某个协议而已,你可以通过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 报错

    目前和财付通很多前端产品一样,我们接入了财付通统一日志上报平台,但是我们上传的内容很有限,访问性能也不是很好,也没法看到 js 错误的调用队列,甚至没法看到用户的 useragent 。在使用我们自己的 Logline 上报系统后,这一切都得到了解决,发现了一些由微信 jssdk 自身产生的错误如 'WeixinJSBridge is not defined'和一些上报量比较大的 js 错误,有效的保证了代码的质量,甚至还可以做一个错误实时曲线统计。

    基于配置的主动抓取

    由于产品正处于快速的功能迭代中,目前不论是业务代码还是核心模块的代码都会经常被改动,不可避免的会产生一些意想不到的问题。有时候,我们会去找到用户希望用户配合帮忙定位问题,但是并不是所有的用户都会愿意配合。于是我们设计了一套主动抓取用户日志的策略:在我们的产品页面被打开后,会延时一定的时间去下载一个 logcat.json 的文件,文件中会包含我们期望可以主动上报日志的用户列表,当和用户吻合时,该用户的日志将会被自动上传。在需要的时候,我们只需要维护这样一个配置文件即可,当配置中的用户日志被上传后,我们将会受到一封邮件提醒。也可以使用此配置文件来配置客户端 JS 是否要自动上报 js 错误。

    CONTRIBUTING

    我们相信前端的社区应该是开放和合作的。

    Logline - 日志记录和上报模块

    logline

    Logline-viewer - 日志分析平台

    logline-viewer

    WIP

    [ ] 不使用 webpack 编译,以减小打包文件的大小 [√i] 加入 bower 包管理器 [ ] 丰富英文代码注释 [ ] 完善错误处理细节 [ ] 测试环境由 jsdom 迁移至 phantomjs [ ] 优化存取结构

    未来规划

    在这次的实践中,我们有意保持模块的独立性,并没有使用任何外部依赖,因此很容易复用到别的业务或者绑定自己的业务逻辑。不过在未来,我们更喜欢 Logline 作为前端日志的一整套解决方案提供,而无需业务在上传和分析部分要做出自己的实现。

    更为可用的应用场景应该是业务放在我们的平台上申请一个 appid ,然后使用我们的统计脚本,所以日志将统一上报到我们的平台,业务方可以使用此 appid 在我们的系统中浏览、分析和检索所需的日志。

    本方案使用的技术和实现难度并不困难,重要的是一种解决痛点的思路。

    4 条回复    2016-12-08 10:36:21 +08:00
    Laygle
        1
    Laygle  
       2016-12-08 01:14:28 +08:00
    很强大,解决了很多人的困扰啊,感谢分享。
    peneazy
        2
    peneazy  
       2016-12-08 06:44:45 +08:00 via Android
    mark
    wensonsmith
        3
    wensonsmith  
       2016-12-08 09:11:56 +08:00
    不错,正需要用到,研究下
    jsq2627
        4
    jsq2627  
       2016-12-08 10:36:21 +08:00 via iPhone
    mark
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     896 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 29ms UTC 20:11 PVG 04:11 LAX 13:11 JFK 16:11
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86