首页 > 代码库 > Log4js原理解析

Log4js原理解析

Log4js原理解析

 

基于log4js 0.6.14版本

 

    Log4js总共三篇博客

    《Log4js原理解析》http://write.blog.csdn.net/postedit/42844085

    《Log4js配置详解》http://blog.csdn.net/hfty290/article/details/42843737

    《Log4js多进程陷阱与避免》http://blog.csdn.net/hfty290/article/details/42843303

一、概述

    网络上有不少关于Log4j的源码解析文章,但是到目前为止还未见到一个log4js的源码解析,虽然这两者有其共同之处,但是在实现原理是存在显著的差别。作为在node.js世界里最流行的日志模块,了解其内部设计与实现还是挺有意义的。本篇将描述log4js的架构与实现,先简要说明log4js中出现的元素,接着为每个元素做详细说明,最后分析元素的协同工作。


二、设计元素简述

    在log4js中出现的设计元素包括:level、layout、appender、logger;请看下面表格:

 技术分享

 

三、色彩缤纷的Appender

    不同的Appender实现不同的日志写入方式,所有的Appender都在源码的 lib/appenders目录下,目前log4js提供了很多种的写入方式,有file、datefile、multiprocess、console、clustered、gelf、hookio、loggly、smtp。本文将依次介绍前五种Appender。每个Appender的js文件都会导出appender和configure两个函数,file与dateFile还会导出shutdown函数。

appender函数返回的是一个闭包函数,该闭包函数实现将内容写入到日志。典型的appender函数实现如下:


function fileAppender (file, layout, logSize, numBackups) {
       …….
       var logFile = openTheStream(file, logSize, numBackups); 
return function(loggingEvent) {
  logFile.write(layout(loggingEvent) + eol, "utf8");
};
}

    而configure函数根据参数提供配置信息,去调用对应的appender函数,将appender函数的返回值作为自己的返回值,如下:


function configure(config, options) {
  ……
  return fileAppender(config.filename, layout, config.maxLogSize, config.backups);
}


    因此configure返回值是一个闭包函数,通过该函数可以实现将日志写入到文件之中。

 

    下面将依次介绍每个Appender的实现:

1、file:实现将日志写入到文本文件之中,同时支持日志文件按照大小滚动。

 技术分享

 

2、datefile:实现将日志写入到文本文件之中,日志按照日期进行滚动。

 技术分享

 

3、console:实现将日志写入到控制台。

 技术分享

 

4、multiprocess:实现多进程同步方式写日志,具体是在master(自主配置)进程上开启一个监听端口,所有的worker进程将日志通过tcp发送给master,由master将日志写入到文件中。注意:这种模式只支持配置一个appender,不能配置多个。

Master配置参数:

 技术分享

 

Worker配置参数:

 技术分享

5、clustered:用于node的cluster环境之中,实现方式与multiprocess类似,真正的写日志是在Master中,Worker只是将日志发送给Master。Worker和Master的配置一样,内部根据cluster.isMaster可以自动判断。

配置参数:

 技术分享

 

四、布局Layout

 

    Layout实现每条日志记录的格式化,log4js提供了多种的格式化样式可供选择,有basicLayout、messagePassThroughLayoutpatternLayoutcolouredLayout、coloredLayout,默认情况下会使用basicLayout。所有的Layout都在源码的lib/layouts.js中定义。layouts.js文件除了导出上述说到的这些Layout,还导出一个layout函数,定义如下:

layout: function(name, config) {
    return layoutMakers[name] && layoutMakers[name](config);
}
 
layoutMakers = {
  "messagePassThrough": function() { return messagePassThroughLayout; }, 
  "basic": function() { return basicLayout; }, 
  "colored": function() { return colouredLayout; }, 
  "coloured": function() { return colouredLayout; }, 
  "pattern": function (config) {
    return patternLayout(config && config.pattern, config && config.tokens);
  }
}

实现将一个文本描述的Layout转换成内部定义的Layout函数。使用起来就像这样:
layout = layouts.layout(config.layout.type, config.layout);
其中的config.layout.type字段表示Layout的名称,而config.layout中的其他字段为对应Layout的配置信息。只有创建pattern类型的Layout时才需要其他配置。


下面依次介绍每种Layout的功能。

1、basicLayout:最基础的Layout,一个message通过该basicLayout会变成如下样子:

[startTime] [logLevel] categoryName - message\n


2、colouredLayout、coloredLayout:格式化日志内容,其中包括了颜色信息,颜色是根据每条日志的级别预定义的。每条记录内容与basicLayout一样:

[startTime] [logLevel] categoryName - message\n


3、messagePassThroughLayout:日志内容只包括消息,没有其他字段:

message\n

4、patternLayout:实现日志按照配置进行格式化,该Layout需要两个参数,pattern、tokens;其中的pattern表示格式化字符串,tokens表示自定义函数。

预定义格式化有:

  var replacers = {
    'c': categoryName,
    'd': formatAsDate,
    'h': hostname,
    'm': formatMessage,
    'n': endOfLine,
    'p': logLevel,
    'r': startTime,
    '[': startColour,
    ']': endColour,
    '%': percent,
    'x': userDefined
  };

 

例如,一个patternLayout的配置如下:

"pattern": "%[%r (%x{pid}) %p %c -%] %m%n",

"tokens": {

"pid" : function() { return process.pid; }

}

    其中自定义了tokens为pid,通过%x{pid}来引用。注意pattern的的 %[ 与 %] 表示颜色的开始于结束。上述配置打印出来的日志如下:

18:13:39 (19556) INFO app - Test log message

 

五、Logger对象

 

    Logger对象实现对日志Level的管理,并定义了对外的写日志接口。客户端通过log4js.getLogger()获取的就是该Logger对象。Logger类从events.EventEmitter继承,因此Logger对象具有发生事件的能力。在log4js之中,写日志是通过在log事件上注册对应的appender来实现的。

Logger对象的组成:

 技术分享

 技术分享

 

 

六、log4js

 

    log4js源码文件为lib/log4js.js,里面包含了一些函数来实现对appender的加载,日志的管理等操作。所有外面要使用log4js中的对象,都采用export的方式导出,可以直接引用,导出的成员如下:

 技术分享

 

1、日志管理

    log4js中为日志进行类别划分,每个类别下最多可以创建一个Logger。同样每个appender实例也有归属的类别,但是一个appender实例可以同时属于多个类别。如图1:

 

 技术分享

 

    图1:有两个category分别为cheese与bread;每个category最多对应着一个logger对象。图中有三个appender,cheese.log与cheese1.log类型为file,另外还有一个console。console这个appender同时指向了cheese和bread,也就是这两个日志都会使用到console。另外cheese有三个appender指向它,意味着,如果想cheese分类的日志中写日志,会同时向三个地方写入,cheese.log, cheese1.log, console.

2、log事件

    前面已经说明,调用Logger实例的写日志操作,会触发log事件。appender在log事件中被调用,从而实现记录写入到日志文件中。如图1中类型为Cheese的Logger,关联着三个appender实例,fileAppender-cheese.log,fileAppender-cheese1.log,console。那么当调用该logger写日志函数,如logger.info时,触发了log事件,与其关联的三个appender实例被调用,最终这条日志内容被写入到三个地方。

 技术分享

     图2:Logger的事件监听机制;用户在调用logger.info(hello‘); 触发了Logger实例的log事件,所有类别为Cheese的Appender实例(有三个),都会监听到该事件,实现将日志记录写入到三个位置。另外还有一种为all的分类,如果指定一个appender的类型为all,那么将收到所有logger的log事件,log4js默认加载的console就是类型为all的appender。

3、替换console

    在开发的过程中,为了方便可能直接将日志直接以console.log方式打印出来,使用log4js可以将console的日志重定向到日志文件中。log4js导出了两个函数:

 技术分享

 

4、日志配置定期检查更新

    log4js提供了自动重新加载日志配置,更新所有的appender与日志级别的信息,是一项非常使用的功能。log4js导出的configure函数中,第二个参数如果设置了reloadSecs,则会在指定的间隔秒数之后,重载配置。如下:

log4js.configure('file.json', { reloadSecs: 300 });

5、其他导出函数说明

 技术分享

 

 

    Log4js总共三篇博客

    《Log4js原理解析》http://write.blog.csdn.net/postedit/42844085

    《Log4js配置详解》http://blog.csdn.net/hfty290/article/details/42843737

    《Log4js多进程陷阱与避免》http://blog.csdn.net/hfty290/article/details/42843303

 

 

 

 


Log4js原理解析