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获取文本节点之 text()/val()/html() 方法区别
Mar 01 Javascript
jquery图片放大镜功能的实例代码
Mar 26 Javascript
js中window.open()的所有参数详细解析
Jan 09 Javascript
js中将String转换为number以便比较
Jul 08 Javascript
小米公司JavaScript面试题
Dec 29 Javascript
在JavaScript中操作时间之getUTCDate()方法的使用
Jun 10 Javascript
JavaScript中构造函数与原型链之间的关系详解
Feb 25 Javascript
详解微信小程序的不同函数调用的几种方法
May 08 Javascript
Vue配置marked链接添加target="_blank"的方法
Jul 19 Javascript
20道JS原理题助你面试一臂之力(必看)
Jul 22 Javascript
Node登录权限验证token验证实现的方法示例
May 25 Javascript
vue.js click点击事件获取当前元素对象的操作
Aug 07 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
CakePHP去除默认显示的标题及图标的方法
2008/10/22 PHP
PHP禁止页面缓存的代码
2011/10/23 PHP
PHP 如何获取二维数组中某个key的集合
2014/06/03 PHP
thinkPHP实现MemCache分布式缓存功能
2016/03/23 PHP
PHP文件与目录操作示例
2016/12/24 PHP
php简单处理XML数据的方法示例
2017/05/19 PHP
浅谈Laravel核心解读之Console内核
2018/12/02 PHP
ajax请求get与post的区别总结
2013/11/04 Javascript
Node.js 学习笔记之简介、安装及配置
2015/03/03 Javascript
小心!AngularJS结合RequireJS做文件合并压缩的那些坑
2016/01/09 Javascript
Nodejs--post的公式详解
2017/04/29 NodeJs
BootStrap 导航条实例代码
2017/05/18 Javascript
vue 怎么创建组件及组件使用方法
2017/07/27 Javascript
Angular 作用域scope的具体使用
2017/12/11 Javascript
微信小程序实现给嵌套template模板传递数据的方式总结
2017/12/18 Javascript
ElementUI 修改默认样式的几种办法(小结)
2020/07/29 Javascript
python实现的防DDoS脚本
2011/02/08 Python
详解C++编程中一元运算符的重载
2016/01/19 Python
Python 多线程搜索txt文件的内容,并写入搜到的内容(Lock)方法
2019/08/23 Python
opencv resize图片为正方形尺寸的实现方法
2019/12/26 Python
以SQLite和PySqlite为例来学习Python DB API
2020/02/05 Python
CSS3实现div从下往上滑入滑出效果示例
2020/04/28 HTML / CSS
详解HTML5 LocalStorage 本地存储
2016/12/23 HTML / CSS
澳大利亚快时尚鞋类市场:Billini
2018/05/20 全球购物
彪马俄罗斯官网:PUMA俄罗斯
2019/07/13 全球购物
澳大利亚著名的纺织品品牌:Canningvale
2020/05/05 全球购物
.net软件工程师应聘上机试题
2015/03/10 面试题
网络通讯中,端口有什么含义,端口的取值范围
2012/11/23 面试题
上级检查欢迎词
2014/01/18 职场文书
遵纪守法演讲稿
2014/05/23 职场文书
医院科室评语
2015/01/04 职场文书
小学班主任自我评价
2015/03/11 职场文书
运动会表扬稿范文
2015/05/05 职场文书
Nginx进程管理和重载原理详解
2021/04/22 Servers
SpringBoot整合Redis入门之缓存数据的方法
2021/11/17 Redis
Nginx流量拷贝ngx_http_mirror_module模块使用方法详解
2022/04/07 Servers