Node.js+ELK日志规范的实现


Posted in Javascript onMay 23, 2019

一般前端开发同学,对日志其实不太敏感,毕竟前端大多数情况下,不太关心日志。即使有,也可能调用一些第三方的统计,比如百度统计或者别的等。在 Node.js(下文中简称node) 推进过程中,也发现我们平常打日志太随意,该打的日志没有打,打的一些关键日志缺少必要上下文信息,导致在线上定位问题的时候很困难。

本文主要梳理了目前我们团队在nodejs开发中日志方面存在的问题,以及通过统一日志规范,希望达到什么样的效果。

问题

  • node日志不规范,打日志太随意
  • 没有良好的日志格式、约定的字段,在 ELK 里不能很好的解析&检索 (PS: ELK文章在路上)
  • 由于node对接的后端服务化,调用链不清晰,定位问题困难
  • 数据部门对node日志的使用,没有明确的记录。node修改了日志,导致统计数据异常

目标

  • 规范日志打印字段&格式,便于 ELK 检索
  • 增强node上下游(nginx/后端)日志格式,加入惟一 requestId,方便微服务下定位问题
  • 统计应用运行情况,性能数据
  • 维护数据部门对node日志的使用情况

实现方案

日志类型

参考一些日志的最佳实践,目前将node日志分为如下几种类型(scope):

  • desc: 系统启动、运行过程中,打的日志,表明系统的一些启动日志、启动参数等,也包含在 不能 捕获到http上下文的时候,打的日志
  • stat: 系统性能统计日志,应用会定时收集一些性能信息,便于查询应用当前状态
  • visit: 每个http请求相关的日志,会包含惟一的 requestId,定位该请求相关的所有日志
  • biz: 业务数据相关日志,主要提供给数据统计使用

日志级别

只使用 FATAL、ERROR、WARN、INFO 和 DEBUG 等级。

  • FATAL - 导致程序退出的严重系统级错误,不可恢复,当错误发生时,系统管理员需要立即介入,一般应用代码 不 使用。
  • ERROR - 运行时异常以及预期之外的错误,也需要立即处理,但紧急程度低于FATAL,当错误发生时,影响了程序的正确执行。需要注意的是这两种级别属于服务自己的错误,需要管理员介入,用户输入出错不属于此分类,请求后端、读文件、数据库等超时、返回错误结构,属于ERROR
  • WARN - 预期之外的运行时状况,表示系统可能出现问题。对于那些目前还不是错误,然而不及时处理也会变成错误的情况,也可以记为WARN,如磁盘过低。
  • INFO - 有意义的事件信息,记录程序正常的运行状态,比如收到请求,成功执行。通过查看INFO,可以快速定位WARN,ERROR, FATAL。INFO不宜过多,通常情况下不超过 DEBUG 的10%。
  • DEBUG - 与程序运行时的流程相关的详细信息以及当前变量状态。

日志格式/字段

  • 日志格式统一采用 JSON ,便于 ELK 解析处理。
  • 日志中的各个字段的值,都应该尽量使用 英文 ,不使用中文。
  • 日志具体字段,分为 基础数据 + 扩展数据。基础数据,是底层日志框架自带的,所有日志都会包含。扩展数据,不同类型的日志,包含不同的字段。

日志基础数据

目前使用的 node-bunyan 日志库,官方文档,基础字段包含如下:

  • v: integer 。bunyan的日志版本号
  • level: integer。日志级别对应的数字
  • name: string。服务名
  • hostname: string。主机名
  • pid: integer。进程号
  • time: string。UTC 格式的日期
  • msg: string。日志主体信息

日志扩展数据

下面定义的各个数据类型的扩展数据,不是 全部的字段,仅包含该日志类型下,必需的字段。这些必需的扩展字段,需要在 ELK 中建立索引,方便定位各种问题。

  • desc类型日志,扩展字段:TODO
  • stat类型日志,扩展字段:{ perf: {rss: xxxx, cpu: xxx} }
  • visit类型日志,扩展字段:
  • biz
{
  ///////////// 基础数据 ////////
  v: 1,
  level: 20,
  ///////////// 扩展字段 ////////
  // 标志日志类型
  scope: "visit",
  //事件类型:在 visit 的日志类型下,还会细分不同的事件,比如 client-req、client-res、 普通trace、请求后端service-start, service-end, service-err等。
  event: "trace",
  //客户端ID,追踪用户、设备会话。在web端,可以是长期的cookie;在APP端,可以是device-id等
  rrdid: "",
  //本次请求的惟一ID,串联本次请求的所有相关日志
  req_id: "some-uuid-for-request",
  //本次请求的用户ID
  uid: "",
  //本次请求的客户端相关数据,通过 ctx.logger 打日志时,自动加上
  d: {
    url: "/some/path?include-query",
    //客户端ip
    ip: "10.138.10.1",
    //客户端的 userAgent
    ua: ""
  },
  //本次node请求的处理时间,毫秒
  tm: 500,
  //该日志相关的上下文数据,尽量拼成一个字符串,放在 extra 里
  extra: "",

  //ERROR 级别日志,最好包含error相关信息,比如请求后端相关参数等
  err: {
    msg: "",
    stack: ""
  },

  //调用后端服务相关参数和响应
  service_req: {
    host: "",
    path: "",
    payload: ""
  },
  service_res: {
    //http状态码
    http_code: 200,
    //响应时间
    tm: 100,
    //响应的body
    body: "",
    //异常信息
    err: ""
  }
}

什么时候打日志

开发者目前只关心 visit 类型的日志,即和某一次http请求相关联的日志。desc和stat类型的日志,统一由开发框架封装后实现,业务开发 不用 关心。下面讲的,都是针对 visit 类型的日志。
一次http请求,会打出一系列相关联的日志。在node层,通常一次请求,会进一步转发给N个后端服务,然后对后端数据进行一些处理、合并等操作,最后渲染页面或是输出JSON。因此,一次请求相关的日志,大体分为以下几种 event:

  • client-req: client请求到达node层,统一由框架打日志,开发 不 关心
  • service-start: node对某个后端服务发起请求,由通用请求库负责打日志,开发 不 关心
  • service-end: node请求某个后端服务结束,由通用请求库负责打日志,开发 不 关心
  • service-err: node请求后端服务异常,由通用请求库负责打日志,开发 不 关心。调用后端服务异常,日志级别为 WARN,不是 ERROR
  • trace: node中业务层打的日志,如果异常,能帮助定位本次请求相关问题
  • client-res: 结束client的请求,打印本次请求的http code,本次请求处理时间等,由框架统一打,开发 不 关心

开发同学在打日志时,应该谨慎的选择级别,INFO(含)级别以上,都应该能对定位问题、具体业务统计需求有要求,才能使用。大部分情况下,可以使用 DEBUG 级别,线上 不会 开启DEBUG级别。

具体方法调用

针对打印 visit类型的日志,调用 ctx.logger(基于Koa的框架) 属性打日志,推荐参数都传递 JSON,具体方法如下:

ctx.logger.debug({msg: "", "extra": "a=1 b=2 c=value"});
ctx.logger.info({msg: "xxx", "extra": "其他的额外字段"});
ctx.logger.warn({msg: "xxx", "extra": "额外上下文数据"});
//ERROR级别日志,应该提供 Error 对象
ctx.logger.error({msg: 'xxx', err: error, extra: ""});

注意1,额外的参数,推荐存放在 extra 字段中,统一拼成 string;如果确实有必要单独出每个字段, 禁止 额外的参数占用上述通用字段名!!

注意2,基础数据中的msg字段,禁止 包含具体的上下文数据,和该日志相关的上下文数据,应该拼成字符串,放在单独的 extra 字段中。比如,某个用户登录接口,希望统计调用次数,可以这样打印:

ctx.logger.info({msg: "user login", "extra": 'mobile=18712387101 code=xxxx k3=value3'});

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Jquery中dialog属性小记
Sep 03 Javascript
jQuery表单获取和失去焦点输入框提示效果的实例代码
Aug 01 Javascript
javascript页面动态显示时间变化示例代码
Dec 18 Javascript
Jquery通过JSON字符串创建JSON对象
Aug 24 Javascript
JavaScript实现下拉列表框数据增加、删除、上下排序的方法
Aug 11 Javascript
js操作table元素实现表格行列新增、删除技巧总结
Nov 18 Javascript
Bootstrap入门书籍之(零)Bootstrap简介
Feb 17 Javascript
更高效的使用JQuery 这里总结了8个小技巧
Apr 13 Javascript
微信小程序 点击控件后选中其它反选实例详解
Feb 21 Javascript
js模拟支付宝密码输入框
Apr 11 Javascript
vue插件mescroll.js实现移动端上拉加载和下拉刷新
Mar 07 Javascript
JavaScript工具库之Lodash详解
Jun 15 Javascript
jquery+php后台实现省市区联动功能示例
May 23 #jQuery
js尾调用优化的实现
May 23 #Javascript
浅谈redux, koa, express 中间件实现对比解析
May 23 #Javascript
Express结合Webpack的全栈自动刷新
May 23 #Javascript
ajax跨域访问遇到的问题及解决方案
May 23 #Javascript
简单了解JavaScript异步
May 23 #Javascript
vue项目添加多页面配置的步骤详解
May 22 #Javascript
You might like
其他功能
2006/10/09 PHP
php桌面中心(四) 数据显示
2007/03/11 PHP
php使用正则表达式提取字符串中尖括号、小括号、中括号、大括号中的字符串
2020/04/05 PHP
thinkphp中多表查询中防止数据重复的sql语句(必看)
2016/09/22 PHP
Thinkphp5 自定义上传文件名的实现方法
2019/07/23 PHP
extjs 学习笔记(三) 最基本的grid
2009/10/15 Javascript
extjs之去除s.gif的影响
2010/12/25 Javascript
js变量以及其作用域详解
2020/07/18 Javascript
javascript针对DOM的应用分析(四)
2012/04/15 Javascript
Javascript计算两个marker之间的距离(Google Map V3)
2013/04/26 Javascript
JS+CSS实现TreeMenu二级树形菜单完整实例
2015/09/18 Javascript
jquery实现的点击翻书效果代码
2015/11/04 Javascript
动态的9*9乘法表效果的实现代码
2016/05/16 Javascript
vue.js中指令Directives详解
2017/03/20 Javascript
angular中的cookie读写方法
2017/08/02 Javascript
详解VUE Element-UI多级菜单动态渲染的组件
2019/04/25 Javascript
vue中的过滤器实例代码详解
2019/06/06 Javascript
layui实现数据表格点击搜索功能
2020/03/26 Javascript
[01:02:06]LGD vs Mineski Supermajor 胜者组 BO3 第二场 6.5
2018/06/06 DOTA
浅谈python新手中常见的疑惑及解答
2016/06/14 Python
python绘制简单折线图代码示例
2017/12/19 Python
python3.5 tkinter实现页面跳转
2018/01/30 Python
点球小游戏python脚本
2018/05/22 Python
转换科学计数法的数值字符串为decimal类型的方法
2018/07/16 Python
利用python修改json文件的value方法
2018/12/31 Python
深入解析HTML5使用SVG图像时的viewBox属性用法
2015/09/02 HTML / CSS
肯尼亚网上商城:Kilimall
2016/08/20 全球购物
奖学金自我鉴定范文
2013/10/03 职场文书
建筑专业自我鉴定
2013/10/22 职场文书
新闻系毕业生推荐信
2013/11/16 职场文书
2015年母亲节活动总结
2015/02/10 职场文书
2015初中政教处工作总结
2015/07/21 职场文书
2016幼儿园毕业感言
2015/12/08 职场文书
2016年“七一建党节”广播稿
2015/12/18 职场文书
JS + HTML 罗盘式时钟的实现
2021/05/21 Javascript
python3.7.2 tkinter entry框限定输入数字的操作
2021/05/22 Python