Log4j主要有三個元件:
Logger:負責供用戶端代碼調用,執行debug(Object msg)、info(Object msg)、warn(Object msg)、error(Object msg)等方法。
Appender:負責日志的輸出,Log4j已經實作了多種不同目标的輸出方式,可以向檔案輸出日志、向控制台輸出日志、向Socket輸出日志等。
Layout:負責日志資訊的格式化。
執行順序及關系
調用Log4j輸出日志時,調用各個元件的順序:
1、日志資訊傳入 Logger。
2、将日志資訊封裝成 LoggingEvent 對象并傳入 Appender。
3、在 Appender 中調用 Filter 對日志資訊進行過濾,調用 Layout 對日志資訊進行格式化,然後輸出。
Log4j 中三種設定配置資訊的方式:
1、利用系統提供的 BasicConfigurator
2、利用 properties 格式的配置檔案,通過 PropertyConfigurator 類來從一個 perproties 檔案中加載配置資訊。
3、利用 XML 檔案來配置資訊,通過 DOMConfigurator 類來從一個 XML 檔案中加載配置資訊。
下面的圖檔來自于網絡:

Log4j如何加載配置檔案,并進行日志記錄?
将Log4j從網上down下來,并建立工程,将源代碼導進去。從Logger入手,一般來說,取得一個Log都是通過
Java代碼
- Logger getLogger(Class clazz) {
- return LogManager.getLogger(clazz.getName());
- }
轉入LogManager,首先應該注意的是這個類的static塊, 這個塊其實就是一個加載log4j配置檔案的過程。即在程式啟動之初,在JVM需要加載這個類時,這個初始化塊會自動運作,并且加載整個配置,以完成log4j的啟動。
- Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));
- ......
- OptionConverter.selectAndConfigure(url, configuratorClassName,
- LogManager.getLoggerRepository());
上面的所有代碼均完成兩件事,第一件事就是構造一個ROOT的Logger對象,此對象作為所有logger對象的最上層,其它logger的相應
屬性均從這個對象進行繼承或改寫,就好像java裡的繼承一樣,這個類是内定的,不能由配置檔案直接指定,且root都是在最頂層的,其他logger均
在此下,這樣形成一個完整的logger樹。下層可以引用上層,上層管理下層。
第二件事則是去尋找配置檔案的位址資訊,通過各種方法都尋找log4j.xml或log4j.properties檔案,然後對檔案進行解析。(在此處,
通過properties檔案進行解析)
- OptionConverter.void selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy)
這個方法會最終通過指定的檔案解析類(此處是PropertyConfigurator)進行解析,轉入
- configurator.doConfigure(url, hierarchy)
這個方法将,url轉化成一個properties對象,進行解析。
- doConfigure(props, hierarchy);
進入這個方法
- String value = properties.getProperty(LogLog.DEBUG_KEY);//即log4j.debug
- if(value == null) {
- value = properties.getProperty("log4j.configDebug");
- if(value != null) {
- LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true));
- }
上面方法讀取一個關于log4j自身的debug
Level資訊,主要用于log4j内部在解析時調用(因為log4j還不能使用logger對象進行寫資訊,它用到一個LogLog的類,來模拟
logger記錄,用于在自身解析的過程中輸出一些資訊),一般來說,這個都用不到。
主要的資訊集中在以下三句話:
- configureRootCategory(properties, hierarchy);
- configureLoggerFactory(properties);
- parseCatsAndRenderers(properties, hierarchy);
第一句話用于解析root根對象上的相關配置。
第二句話用于解析loggerFactory(factory用于建立logger對象)
第三句話用于解析除rootLogger之外的其他logger以及render資訊。
第一句:
- void configureRootCategory(Properties props, LoggerRepository hierarchy) {
- String effectiveFrefix = ROOT_LOGGER_PREFIX;
- String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props);
- if(value == null) {
- value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props);
- effectiveFrefix = ROOT_CATEGORY_PREFIX;
- }
- .....
- Logger root = hierarchy.getRootLogger();
- synchronized(root) {
- parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);
- }
上面語句用于從屬性檔案中尋找logger.rootLogger或logger.rootCategory屬性的值然後,再進行配置rootLogger對象資訊(如Level,appender等)
- void parseCategory(Properties props, Logger logger, String optionKey,
- String loggerName, String value) {
- StringTokenizer st = new StringTokenizer(value, ",");
- if(!(value.startsWith(",") || value.equals(""))) {
- if(!st.hasMoreTokens())
- return;
- String levelStr = st.nextToken();
- if(INHERITED.equalsIgnoreCase(levelStr) ||
- NULL.equalsIgnoreCase(levelStr)) {
- if(loggerName.equals(INTERNAL_ROOT_NAME)) {
- LogLog.warn("The root logger cannot be set to null.");
- } else {
- logger.setLevel(null);
- } else {
- logger.setLevel(OptionConverter.toLevel(levelStr, (Level) Level.DEBUG));
- }
- logger.removeAllAppenders();
- Appender appender;
- String appenderName;
- while(st.hasMoreTokens()) {
- appenderName = st.nextToken().trim();
- appender = parseAppender(props, appenderName);
- if(appender != null) {
- logger.addAppender(appender);
- }
上面這些内容,即是通過logger屬性的值(形如debug,A,R)等,然後将值以,分隔進行解析,将第一個字元串定義為logger的Level資訊,後面的值則定義為此logger的appender名稱。
當然上面這個方法,即不是盡為rootLogger服務,對于其他logger也調用這個方法,故上面在解析Level時,對root作了單獨判斷(因為
rootLogger的Level不能為空,至少均需要一個值)。處理Level,即将level值簡單設定在logger上即可以了。
接下來即解析appender資訊,通過緊接着level後面的字元串,按清單方式進行解析,然後将appender加入logger的appenderList(即要進行資訊處理的監聽器表)中。進入parseAddpender(解析appender)
- String prefix = APPENDER_PREFIX + appenderName;
- String layoutPrefix = prefix + ".layout";
- appender = (Appender) OptionConverter.instantiateByKey(props, prefix,
- org.apache.log4j.Appender.class,
- null);
- ...
- appender.setName(appenderName);
- if(appender instanceof OptionHandler) {
- if(appender.requiresLayout()) {
- yout layout = (Layout) OptionConverter.instantiateByKey(props,
- layoutPrefix,
- Layout.class,
- null);
- appender.setLayout(layout);
- PropertySetter.setProperties(layout, props, layoutPrefix + ".");
- LogLog.debug("End of parsing for \"" + appenderName +"\".");
- PropertySetter.setProperties(appender, props, prefix + ".");
- registryPut(appender);
上面這個方法,即是解析appender對象,通過log4j.appender.X的字首(X表示在rootLogger後的appender
名稱)來取得appender類名,并嘗試執行個體化,然後根據appender來判斷是否需要再解析appender的layout(即
log4j.appender.X.layout這個鍵),解析并設定相應屬性,最後分别解析appender本身的屬性資訊和layout的屬性資訊。
(通過ProperSetter這個類,根據javaBean屬性映射,将指定字尾後的資訊當作一個鍵,字尾在屬性檔案中的值作為指定鍵的值,并将這個鍵
值映射,通過javaBean設定到相應的對象上)
至此,rootLogger即解析完畢。
第二句:解析loggerFactory,略。
第三句:解析其他logger資訊。
- void parseCatsAndRenderers(Properties props, LoggerRepository hierarchy) {
- Enumeration enumeration = props.propertyNames();
- while(enumeration.hasMoreElements()) {
- String key = (String) enumeration.nextElement();
- if(key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) {
- String loggerName = null;
- if(key.startsWith(CATEGORY_PREFIX)) {
- loggerName = key.substring(CATEGORY_PREFIX.length());
- } else if(key.startsWith(LOGGER_PREFIX)) {
- loggerName = key.substring(LOGGER_PREFIX.length());
- String value = OptionConverter.findAndSubst(key, props);
- Logger logger = hierarchy.getLogger(loggerName, loggerFactory);
- synchronized(logger) {
- parseCategory(props, logger, key, loggerName, value);
- parseAdditivityForLogger(props, logger, loggerName);
- } else if(key.startsWith(RENDERER_PREFIX)) {
這個方法,則主要根據logger.logger為字首的屬性資訊進行解析,并根據這個屬性資訊生成logger,并放在繼承樹中,設定相應樹資訊,最後設定其他資訊(如appender,level)等,與rootLogger解析基本一緻。