Log4js 工作原理及代碼簡析


本文地址 http://www.cnblogs.com/jasonxuli/p/6518650.html
 
log4js
 
版本 0.6.16, 最新版1.1.1 大體類似。
 
使用 log4js 時,基本的流程是:
1,聲明 config 配置;
2,log4js.configure(config);
3, log4js.getLogger(categoryName);
 
配置
 
主要有下面幾個要點:
 
type: 表明 appender 的類型,對應 log4js/lib/appenders/ 目錄下的文件名, log4js 會在加載配置文件時根據 type 加載對應文件。
      categoryFilter.js, clustered.js, console.js, dateFile.js, file.js, fileSync.js, gelf.js, hookid.js, loggly.js, logLevelFilter.js, multiprocess.js, smtp.js
          
category:表明 appender 的分類,用戶自定義;如果不指定,加載時默認值為 [all];相同 category 的 appender 會被添加到內部變量  appenders {category : appender} 中。這個 appenders 的作用體現在之后通過 getLogger() 獲取 logger 時;
 
level: log4js 的 level 如下
module.exports = {
  ALL: new Level(Number.MIN_VALUE, "ALL"),
  TRACE: new Level(5000, "TRACE"),
  DEBUG: new Level(10000, "DEBUG"),
  INFO: new Level(20000, "INFO"),
  WARN: new Level(30000, "WARN"),
  ERROR: new Level(40000, "ERROR"),
  FATAL: new Level(50000, "FATAL"),
  OFF: new Level(Number.MAX_VALUE, "OFF"),
  toLevel: toLevel
};

 

appender 結構:參看下面的示例,簡單類型例如 file,fileSync等,只需要簡單的對象,不需要內部再嵌套一個 appender; 對於復雜類型例如 logLevelFilter,categoryFilter等,最終還是需要一個簡單類型去寫日志,因此需要嵌套一個 appender 。
 
var config = {
    appenders    : [
        {
            type: "console"
        },
        {
            type    : "file",
            filename: "/var/log/kernel/test.log"
        },
        {
            type    : "logLevelFilter",
            level  : "ALL",
            appender: {
                type    : "file",
                filename: "/var/log/kernel/kernel.log",
                layout:{
                    type:"pattern",
                    pattern: "[%h %x{pid}] - [%d] [%p] %c %m",
                    tokens: {
                        pid: function(){return process.pid}
                    }
                }
            }
        },
        {
            type    : "logLevelFilter",
            level  : "ERROR",
            appender: {
                type    : "file",
                filename: "/var/log/kernel/kernelerr.log"
            }
        },
        {
            type    : "file",
            filename: "/var/log/kernel/cron.log",
            category: "cron"
        },
        {
            type    : "file",
            filename: "/var/log/kernel/mem.log",
            category: "memory"
        }
    ],
    replaceConsole: true
};
 
log4js.configure(config);

 

  配置加載流程
 
根據log4js.js 中的代碼次序,關鍵的函數是下面幾個 : 
 
1,configure() : 
      入口函數
 
2,loadAppender(appender, appenderModule) :
     根據 type 加載 log4js/lib/appenders/ 目錄下對應的 appender 模塊;之后調用該模塊的 configure() 加載該 appender 配置,返回最終負責寫 log 的函數 function(loggingEvent); 因此 logLevelFilter 這樣的"高級"模塊就會尋找 config.appender 屬性進行后續配置;
 
3,addAppenderToCategory(appender, category)
     將 category 和加載后的 appender 作為鍵值對添加到 appenders 對象;
 
4,addAppenderToAllLoggers(appender)
     將沒有指定 category 的 appender 默認為 [all],添加到 logger 緩存對象 loggers 中; 
 
其實不太明白這里為什么要先添加 [all] 到 loggers 緩存中,畢竟 getLogger() 函數中 categoryName 的默認值是 [default];為什么不統一都用 [default] 或者 [all],至少相當於預熱緩存了。
     
 
logger和appender的關系
 
主要體現在下面的函數中:
function getLogger (categoryName) {
 
  // Use default logger if categoryName is not specified or invalid
  if (typeof categoryName !== "string") {
    categoryName = Logger.DEFAULT_CATEGORY;
  }
 
  var appenderList;
  if (!hasLogger(categoryName)) {
    // Create the logger for this name if it doesn't already exist
    loggers[categoryName] = new Logger(categoryName);
    if (appenders[categoryName]) {
      appenderList = appenders[categoryName];
      appenderList.forEach(function(appender) {
        loggers[categoryName].addListener("log", appender);
      });
    }
    if (appenders[ALL_CATEGORIES]) {
      appenderList = appenders[ALL_CATEGORIES];
      appenderList.forEach(function(appender) {
        loggers[categoryName].addListener("log", appender);
      });
    }
  }
 
  return loggers[categoryName];
}

其中,loggers 和 appenders 上面說過,一個是是 logger 的緩存map; 一個是 appender 的Map。

 
getLogger(categoryName)步驟如下:
1,先去 loggers 中以 categoryName 為 key 找 logger,找到就直接返回,沒有找到就生成一個 new Logger(categoryName),並添加到 logger 緩存。 
2,在 appenders 中以 categoryName 為key 查找 appenderList,找到了就以該 logger 為宿主,將 appenderList 中所有的 appender 添加為 on 事件的監聽器。
3,不管2是否成功,都將 appenders 中 [all] 對應的所有 appender 添加為該 logger 的 on 事件的監聽器。
 
最終兩個Map的結構大致如下:
 
appenders : 
[all]            ->  [apd1, apd2, ...]
category1    ->  [apd3]
category2    ->  [apd4, apd5]
...
 
loggers : 
[all]            ->  loggerA      --addListener()-->  [apd1, apd2, ...]
[default]     ->  loggerB      --addListener()-->  [apd1, apd2, ...]
category1   ->  loggerC      --addListener()-->  [apd3, apd1, apd2, ...]
category2   ->  loggerD      --addListener()-->  [apd4, apd5, apd1, apd2, ...]
...
 
 
因此:
1,category 一樣的 appender 會同時輸出日志;
2,沒有指定 category 的 appender 總是會輸出日志;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM