log4js日志管理(分级、格式化、切割等)使用详解

log4js使用详解

技术选型

在node服务端的日志收集工具中,比较火热的有winston、log4js、bunyan、npmlog等,对于日志收集的基本要求例如分布式集中收集、多渠道输出、日志格式化等上述npm库均可满足。现在分析下上述工具库的特点:

  • winston:非常全面的日志管理库,但比较重一些
  • log4js:定制灵活、比较全面的日志管理库,允许api配置也允许json方式配置,相对轻便、灵活
  • bunyan:日志记录方式为JSON,相对来说形式比较单一
  • npmlog:基础的日志记录器,不具备更多的高级功能、定制化功能。

对于本项目来说,并不需要复杂的winston来支持,log4js在兼顾功能的情况下也可以兼顾性能,因此使用log4js

但是对于express框架的后端,我们可以考虑使用express默认中间组件morgan

log4js基本使用方法

1. 安装

使用npm下载即可

npm install log4js
2. 基本功能

在log4js中使用日志记录器,主要的功能如下:

  • 日志级别:

    日志分级可以将不同级别的日志在控制台中采用不同的颜色,也可以帮助在生产时有选择地生成不同级别的日志。

    这一点理解上可以对应console api的调用,我们可以console.log、console.info、console.err,这是类似的。log4js默认的日志分级有9级,并且不像winston可以自定义配置日志级别,这是不可修改的:

    Level.addLevels({
      ALL: { value: Number.MIN_VALUE, colour: 'grey' },
      TRACE: { value: 5000, colour: 'blue' },
      DEBUG: { value: 10000, colour: 'cyan' },
      INFO: { value: 20000, colour: 'green' },
      WARN: { value: 30000, colour: 'yellow' },
      ERROR: { value: 40000, colour: 'red' },
      FATAL: { value: 50000, colour: 'magenta' },
      MARK: { value: 9007199254740992, colour: 'grey' }, // 2^53
      OFF: { value: Number.MAX_VALUE, colour: 'grey' }
    });
    

其优先级依次递增,有个更清晰的图示:

在这里插入图片描述

ALL OFF 这两个等级并不会直接在业务代码中使用。剩下的七个即分别对应 Logger 实例的七个方法,.trace .debug .info …。也就是说,你在调用这些方法的时候,就相当于为这些日志定了级。

  • 日志类型:
1. 日志分类过滤

对于创建的同一个logger记录器实例,我们可能会遇到需要在记录日志的时候区分info 、 debug 、error等不同级别的日志,并将其放在不同的file文件中,例如分别放在xxx-info.log、xxx-debug.log、xxx-error.log三种文件中

这个时候我们需要用到类型为logLevelFilter的过滤器,该过滤器可以对不同级别的日志、指定不同的追加器

同时我们在category类别中,定义以不同的key,使用不同的filter / appender组,让日志根据对应级别匹配其类别下可输出的日志Appender。注意:category配置时,default是必须要有的,代表其他的category没有匹配到时,默认进入default; category通过getLogger函数传参设置,不同类别下的logger,使用不同类别key对应的filter / appender

以下是实现上述需求的一个封装类:

let log4js = require('log4js');
let path = require('path')
let _fs = require('fs')

class Logger {
    constructor() {
        this._logger = null;
        this.logPathName = 'logs'
        this.config = {
            type: 'dateFile',
            filename: path.resolve(this.logPathName + '/info.log'),
            encoding: 'utf-8',
            layout: {
                type: 'pattern',
                pattern: '%d %p %h %m'
            },
            //split by day
            pattern: 'yyyy-MM-dd',
            // ends as '.log' 
            keepFileExt: true,
            //insert the date at the tail of name
            alwaysIncludePattern: true,
        }
    }

    init() {
        //define and configure logger
        log4js.configure({
            appenders: {
                default: this.config,
                //debug appender
                debugAppender: {
                    ...this.config,
                    filename: path.resolve(this.logPathName + '/debug.log'),
                },
                //debug filter
                debugFilter: {
                    type: 'logLevelFilter',
                    appender: 'debugAppender', // assign a specific appender
                    level: 'debug', // lowest log level to catch
                    maxLevel: 'debug', //highest log level to catch
                },
                //info filter
                infoFilter: {
                    type: 'logLevelFilter',
                    appender: 'default',
                    level: 'info',
                    maxLevel: 'info',
                },
                //warn filter
                warnFilter: {
                    type: 'logLevelFilter',
                    appender: 'default',
                    level: 'warn',
                    maxLevel: 'warn',
                },
                errorAppender: {
                    ...this.config,
                    filename: path.resolve(this.logPathName + '/error.log'),
                },
                //error filter
                errorFilter: {
                    type: 'logLevelFilter',
                    appender: 'errorAppender',
                    level: 'error',
                    maxLevel: 'fatal',
                },
            },

          categories: {
                default: {   //default is a must
                    appenders: ['infoFilter', 'warnFilter', 'errorFilter','debugFilter'],
                    level: 'debuug',
                }
            },
        })

        //get logger,无category传参,进入default配置中
        this._logger = log4js.getLogger();
        this._logger.level = "debug"
    }
}
2. 定义日志Logger的不同类型来源

当获取Logger实例时,唯一可以传的参数就是日志类型loggerCategory,这个是非常灵活的、自定义字符串的参数,用于从另一个维度将日志分类。
比如:

// file: set-catetory.js
var log4js = require('log4js');
var logger = log4js.getLogger('set-catetory.js');
logger.debug("Time:", new Date());

打印的日志信息如下:

 [2016-08-21 01:24:07.332] [DEBUG] set-catetory.js - Time: 2016-08-20T17:24:07.331Z 

可以从这条日志信息看出这条日志来自于 set-catetory.js 文件。又或者针对不同的 node package 使用不同的 category,这样可以区分日志来源于哪个模块。

  • 日志落盘:

日志落盘通俗来说就是日志在哪里生成,也可以理解为日志输出的渠道。在log4js中日志的出口通过Appender来定义。
log4js提供的Appender有

  • console:输出到控制台
  • file:输出到指定目录文件中
  • dataFile:将日志输出到文件,可以按照特定日期滚动生成,例如今天输出到 default-2023-08-21.log,明天输出到 default-2023-08-22.log
  • multiFile:动态配置不同category下或者不同变量控制下,落盘到不同文件
  • smtp:将日志输出到邮件

默认Appender
下面是 log4js 内部默认的 appender 设置:

// log4js.js
defaultConfig = {
  appenders: [{
    type: "console"
  }]
}

设置自己的Appender
我们可以通过log4js.configure来设置我们想要的 appender。

// file: custom-appender.js
var log4js = require('log4js');
log4js.configure({
  appenders: [{
    type: 'file',
    filename: 'default.log'
  }]
})
var logger = log4js.getLogger('custom-appender');
logger.debug("Time:", new Date());

在上例中,我们将日志输出到了文件中,运行代码,log4js 在当前目录创建了一个名为default.log 文件,[2016-08-21 08:43:21.272] [DEBUG] custom-appender - Time: 2016-08-21T00:43:21.272Z 输出到了该文件中。

上述Appenders也可以见出,Appenders是一个数组,可以接受多个type的输出渠道~

值得一提的multiFile
1、根据category来动态落盘

log4js.configure({
  appenders: {
    multi: {
      type: "multiFile",
      base: "logs/",
      property: "categoryName",
      extension: ".log",
    },
  },
  categories: {
    default: { appenders: ["multi"], level: "debug" },
  },
});

const logger = log4js.getLogger();
logger.debug("I will be logged in logs/default.log");
const otherLogger = log4js.getLogger("cheese");
otherLogger.info("Cheese is cheddar - this will be logged in logs/cheese.log");

2、根据变量来动态落盘(使用addContext设置变量)【下述例子展现了如何例如addContext来使日志进行滚动,下例中根据userId滚动,也可以同理替换为new Date使其根据日期滚动

log4js.configure({
  appenders: {
    everything: {
      type: "multiFile",
      base: "logs/",
      property: "userID",
      extension: ".log",
      maxLogSize: 10485760,
      backups: 3,
      compress: true,
    },
  },
  categories: {
    default: { appenders: ["everything"], level: "debug" },
  },
});

const userLogger = log4js.getLogger("user");
userLogger.addContext("userID", user.getID());
userLogger.info("this user just logged in");

  • 日志过滤:

我们可以调整 appender 的配置,对日志的级别和类别进行过滤:

// file: level-and-category.js
var log4js = require('log4js');
log4js.configure({
  appenders: [{
    type: 'logLevelFilter',
    level: 'DEBUG',
    category: 'category1',
    appender: {
      type: 'file',
      filename: 'default.log'
    }
  }]
})
var logger1 = log4js.getLogger('category1');
var logger2 = log4js.getLogger('category2');
logger1.debug("Time:", new Date());
logger1.trace("Time:", new Date());
logger2.debug("Time:", new Date());

运行,在 default.log 中增加了一条日志:

[2016-08-21 10:08:21.630] [DEBUG] category1 - Time: 2016-08-21T02:08:21.629Z

为什么会出现如上的结果呢?主要是是从日志级别和category分类对应日志:

  • 使用 logLevelFilter 和 level 来对日志的级别进行过滤,所有权重大于或者等于DEBUG的日志将会输出。这也是之前提到的日志级别权重的意义;

  • 通过 category 来选择要输出日志的类别,category2 下面的日志被过滤掉了,该配置也接受一个数组,例如 [‘category1’, ‘category2’],这样配置两个类别的日志都将输出到文件中。

  • 日志格式(Layout):

日志格式layout在每个Appender中的layout属性中配置,有几个默认的显示类型:

  • messagePassThrough:仅仅输出日志的内容;
  • basic:在日志的内容前面会加上时间、日志的级别和类别,通常日志的默认 layout;
  • colored/coloured:在 basic 的基础上给日志加上颜色,appender Console 默认使用的就是这个 layout;
  • pattern:这是一种特殊类型,可以通过它来定义任何你想要的格式。
    一个 pattern 的例子:
// file: layout-pattern.js
var log4js = require('log4js');
log4js.configure({
  appenders: [{
    type: 'console',
    layout: {
      type: 'pattern',
      pattern: '[%r] [%[%5.5p%]] - %m%n'
    }
  }]
})
var logger = log4js.getLogger('layout-pattern');
logger.debug("Time:", new Date());

对于pattern中指定的格式标识符,可以参考官网给出的表格:
在这里插入图片描述
log4js的官方文档和源码地址在下方,有需要可以自己查阅:

官方文档:https://log4js-node.github.io/log4js-node/
源码:https://github.com/log4js-node/log4js-node