Log4js原理解析
基于log4js 0.6.14版本
Log4js总共三篇博客
《Log4js原理解析》http://blog.csdn.net/hfty290/article/details/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、messagePassThroughLayout、patternLayout、colouredLayout、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
[startTime] [logLevel] categoryName - message\n
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://blog.csdn.net/hfty290/article/details/42844085
《Log4js配置详解》http://blog.csdn.net/hfty290/article/details/42843737
《Log4js多进程陷阱与避免》http://blog.csdn.net/hfty290/article/details/42843303