日志框架,jdk-logging(jdk自帶的logging),原生Logger的logging.properties配置文件簡單分析


原生Logger的logging.properties配置文件簡單分析_加倍努力中的博客-CSDN博客_logging.properties

前言

logging.properties配置文件用於原生的日志記錄器進行配置,對該配置文件有一定了解可以更好的使用日志記錄器。

文件路徑

jre/lib/logging.properties

文件概覽

############################################################
# Default Logging Configuration File#
# You can use a different file by specifying a filename
# with the java.util.logging.config.file system property. 
# For example java -Djava.util.logging.config.file=myfile
############################################################
############################################################
# Global properties
############################################################
# "handlers" specifies a comma separated list of log Handler
# classes.  These handlers will be installed during VM startup.
# Note that these classes must be on the system classpath.
# By default we only configure a ConsoleHandler, which will only
# show messages at the INFO and above levels.
handlers= java.util.logging.ConsoleHandler
# To also add the FileHandler, use the following line instead.
handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler
# Default global logging level.
# This specifies which kinds of events are logged across
# all loggers.  For any given facility this global level
# can be overriden by a facility specific level
# Note that the ConsoleHandler also has a separate level
# setting to limit messages printed to the console. 
.level= INFO
############################################################
# Handler specific properties.
# Describes specific configuration info for Handlers.
############################################################
# default file output is in user's home directory.
java.util.logging.FileHandler.pattern = %h/java%u.log
java.util.logging.FileHandler.limit = 50000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
java.util.logging.FileHandler.append = true
# Limit the message that are printed on the console to INFO and above.
java.util.logging.ConsoleHandler.level=INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# Example to customize the SimpleFormatter output format 
# to print one-line log message like this:
#     <level>: <log message> [<date/time>]## 
java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n
############################################################
# Facility specific properties.
# Provides extra control for each logger.
############################################################
# For example, set the com.xyz.foo logger to only log SEVERE
# messages:com.xyz.foo.level = SEVERE
內容分析
# Default Logging Configuration File #
# You can use a different file by s filename
# with the java.util.logging.config.file system property. 
# For example java -Djava.util.logging.config.file=myfile
java -Djava.util.logging.config.file=myfile

如果想要使用另一個配置文件,就要將java -Djava.util.logging.config.file特性設置成配置文件的存儲位置

除了以上方法,還可以利用原生API進行設置,在main中執行

System.setProperty("java -Djava.util.logging.config.file",myfile);

該方法會調用readConfiguration()來重新初始化日志管理器

# "handlers" specifies a comma separated list of log Handler
# classes.  These handlers will be installed during VM startup.
# Note that these classes must be on the system classpath.
# By default we only configure a ConsoleHandler, which will only
# show messages at the INFO and above levels.
handlers= java.util.logging.ConsoleHandler

# To also add the FileHandler, use the following line instead.
handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler

該段配置信息用於指定日志記錄器的處理器,既可以配置一個,也可以通過合適的分隔符“,”來配置多個處理器。

# Default global logging level.
# This specifies which kinds of events are logged across
# all loggers.  For any given facility this global level
# can be overriden by a facility specific level
# Note that the ConsoleHandler also has a separate level
# setting to limit messages printed to the console. 
.level= INFO

由注釋可以得知,.level用於全局設置日志處理器處理對象信息的級別的閾值,這里的設置的級別是INFO

# default file output is in user's home directory.
java.util.logging.FileHandler.pattern = %h/java%u.log
java.util.logging.FileHandler.limit = 50000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
java.util.logging.FileHandler.append = true

這里配置了日志文件的名字格式、文件格式(這里是XML格式)等,
通過注釋,我們可以得知日志文件的存儲位置位於User文件夾。

# Limit the message that are printed on the console to INFO and above.
java.util.logging.ConsoleHandler.level=INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# Example to customize the SimpleFormatter output format 
# to print one-line log message like this:
#     <level>: <log message> [<date/time>]## 
java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n

這里配置的是輸出到日志信息輸出到控制台的最低級別(這里是INFO,必須要注意該級別不能低於全局閾值)、輸出到控制台日志信息的格式

# For example, set the com.xyz.foo logger to only log SEVERE
# messages:com.xyz.foo.level = SEVERE

這里是對自定義的日志記錄器配置,指定日志記錄的記錄級別,低於該級別的日志信息不會被處理。

日志框架1:目前主流的日志框架_Cape_sir-CSDN博客_主流日志框架

目前的日志框架有jdk自帶的logging,log4j1、log4j2、logback

目前用於實現日志統一的框架apache的commons-logging、slf4j

為了理清它們的關系,與繁雜的各種集成jar包,如下:

log4j、log4j-api、log4j-core
log4j-1.2-api、log4j-jcl、log4j-slf4j-impl、log4j-jul
logback-core、logback-classic、logback-access commons-logging
slf4j-api、slf4j-log4j12、slf4j-simple、jcl-over-slf4j、slf4j-jdk14、log4j-over-slf4j、slf4j-jcl

 

日志框架2:jdk-logging(jdk自帶的logging)_Cape_sir-CSDN博客_logging日志框架

1.簡單使用

1.1 示例代碼

private static final Logger logger=Logger.getLogger(JdkLoggingTest.class.getName());

public static void main(String[] args){
    logger.info("jdk logging info: a msg");
}

其中的Logger是:java.util.logging.Logger

1.2 過程分析

1)創建一個LogManager,默認是java.util.logging.LogManager,但是也可以自定義,修改系統屬性"java.util.logging.manager"即可,源碼如下(manager就是LogManager):

try {
    cname = System.getProperty("java.util.logging.manager");
    if (cname != null) {
        try {
            Class clz = ClassLoader.getSystemClassLoader().loadClass(cname);
            manager = (LogManager) clz.newInstance();
        } catch (ClassNotFoundException ex) {
            Class clz = Thread.currentThread().getContextClassLoader().loadClass(cname);
            manager = (LogManager) clz.newInstance();
        }
    }
} catch (Exception ex) {
    System.err.println("Could not load Logmanager \"" + cname + "\"");
    ex.printStackTrace();
}
if (manager == null) {
    manager = new LogManager();
}

2)加載配置文件,默認是jre目錄下的lib/logging.properties文件,也可以自定義修改系統屬性"java.util.logging.config.file”,源碼如下:

String fname = System.getProperty("java.util.logging.config.file");
if (fname == null) {
    fname = System.getProperty("java.home");
    if (fname == null) {
        throw new Error("Can't find java.home ??");
    }
    File f = new File(fname, "lib");
    f = new File(f, "logging.properties");
    fname = f.getCanonicalPath();
}
InputStream in = new FileInputStream(fname);
BufferedInputStream bin = new BufferedInputStream(in);
try {
    readConfiguration(bin);
}

3)創建Logger,並緩存起來,放置到一個Hashtable中,並把LogManager設置進新創建的logger中

修改屬性"java.util.logging.manager”,自定義LogManager
修改屬性"java.util.logging.config.file”,自定義配置文件

2.深入淺出

2.1 提出問題

JDK Logging的使用很簡單,如下代碼所示,先使用Logger類的靜態方法getLogger就可以獲取到一個logger,然后在任何地方都可以通過獲取到的logger進行日志輸入。比如類似logger.info(“Main running.”)的調用。

package com.bes.logging;

import java.util.logging.Level;
import java.util.logging.Logger;

public class LoggerTest {
      private static Loggerlogger = Logger.getLogger("com.bes.logging");
      public static void main(String argv[]) {
               // Log a FINEtracing message
               logger.info("Main running.");
               logger.fine("doingstuff");
               try {
                         Thread.currentThread().sleep(1000);// do some work
               } catch(Exception ex) {
                         logger.log(Level.WARNING,"trouble sneezing", ex);
               }
               logger.fine("done");
      }
}

不做任何代碼修改和JDK配置修改的話,運行上面的例子,你會發現,控制台只會出現【Main running.】這一句日志。如下問題應該呈現在你的大腦里…

1.【Main running.】以外的日志為什么沒有輸出?怎么讓它們也能夠出現?
2. 日志中出現的時間、類名、方法名等是從哪里輸出的?
3. 為什么日志就會出現在控制台?
4. 大型的系統可能有很多子模塊(可簡單理解為有很多包名),如何對這些子模塊進行單獨的日志級別控制?
5. apache那個流行的log4j項目和JDK的logging有聯系嗎,怎么實現自己的LoggerManager?

2.2 術語解答

Logger

1.代碼需要輸入日志的地方都會用到Logger,這幾乎是一個JDK logging模塊的代言人,我們常常用Logger.getLogger(“com.aaa.bbb”);獲得一個logger,然后使用logger做日志的輸出。
2.logger其實只是一個邏輯管理單元,其多數操作都只是作為一個中繼者傳遞別的<角色>,比如說:Logger.getLogger(“xxx”)的調用將會依賴於LogManager類,使用logger輸入日志信息的時候會調用logger中的所有handler進行日志的輸入。
3.logger是有層次關系的,我們可一般性的理解為包名之間的父子繼承關系。每個logger通常以java包名為其名稱。子logger通常會從父logger繼承logger級別、handler、ResourceBundle名(與國際化信息有關)等。
4.整個JVM會存在一個名稱為空的root logger,所有匿名的logger都會把root logger作為其父。

LogManager

1.LogManager:整個JVM內部所有logger的管理,logger的生成、獲取等操作都依賴於它,也包括配置文件的讀取。
2.LogManager中會有一個Hashtable【private Hashtable<String,WeakReference> loggers】用於存儲目前所有的logger,如果需要獲取logger的時候,Hashtable已經有存在logger的話就直接返回Hashtable中的,如果hashtable中沒有logger,則新建一個同時放入Hashtable進行保存。

Handler

1.Handler:用來控制日志輸出的,比如JDK自帶的ConsoleHanlder把輸出流重定向到System.err輸出,每次調用Logger的方法進行輸出時都會調用Handler的publish方法,每個logger有多個handler。
2.我們可以利用handler來把日志輸入到不同的地方(比如文件系統或者是遠程Socket連接)。

Formatter

Formatter:日志在真正輸出前需要進行一定的格式化,比如是否輸出時間?時間格式?是否輸入線程名?是否使用國際化信息等都依賴於Formatter。

Log Level

Log Level:不必說,這是做容易理解的一個,也是logging為什么能幫助我們適應從開發調試到部署上線等不同階段對日志輸出粒度的不同需求。JDK Log級別從高到低為:
OFF(2^31-1) —>SEVERE(1000)—>WARNING(900)—>INFO(800)—>CONFIG(700)—>FINE(500)—>FINER(400)—>FINEST(300)—>ALL(-2^31)
每個級別分別對應一個數字,輸出日志時級別的比較就依賴於數字大小的比較。

但是需要注意的是:不僅是logger具有級別,handler也是有級別,也就是說如果某個logger級別是FINE,客戶希望輸入FINE級別的日志,如果此時logger對應的handler級別為INFO,那么FINE級別日志仍然是不能輸出的。

對應關系

LogManager與logger是1對多關系,整個JVM運行時只有一個LogManager,且所有的logger均在LogManager中。
logger與handler是多對多關系,logger在進行日志輸出的時候會調用所有的hanlder進行日志的處理。
handler與formatter是一對一關系,一個handler有一個formatter進行日志的格式化處理。
很明顯:logger與level是一對一關系,hanlder與level也是一對一關系。

2.3 Logging配置

JDK默認的logging配置文件為:$JAVA_HOME/jre/lib/logging.properties,可以使用系統屬性java.util.logging.config.file指定相應的配置文件對默認的配置文件進行覆蓋,配置文件中通常包含以下幾部分定義:

1.handlers:用逗號分隔每個Handler,這些handler將會被加到root logger中。也就是說即使我們不給其他logger配置handler屬性,在輸出日志的時候logger會一直找到root logger,從而找到handler進行日志的輸入。

2.level是root logger的日志級別。

3..xxx是配置具體某個handler的屬性,比如java.util.logging.ConsoleHandler.formatter便是為ConsoleHandler配置相應的日志Formatter。

4.logger的配置,所有以[.level]結尾的屬性皆被認為是對某個logger的級別的定義,如com.bes.server.level=FINE是給名為[com.bes.server]的logger定義級別為FINE。

順便說下,前邊提到過logger的繼承關系,如果還有com.bes.server.webcontainer這個logger,且在配置文件中沒有定義該logger的任何屬性,那么其將會從[com.bes.server]這個logger進行屬性繼承。

除了級別之外,還可以為logger定義handler和useParentHandlers(默認是為true)屬性,如com.bes.server.handler=com.bes.test.ServerFileHandler(需要是一個extends java.util.logging.Handler的類)。
com.bes.server.useParentHandlers=false(意味着com.bes.server這個logger進行日志輸出時,日志僅僅被處理一次,用自己的handler輸出,不會傳遞到父logger的handler)。

以下是JDK配置文件示例:

handlers= java.util.logging.FileHandler,java.util.logging.ConsoleHandler
.level= INFO
java.util.logging.FileHandler.pattern = %h/java%u.log
java.util.logging.FileHandler.limit = 50000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter=java.util.logging.XMLFormatter
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter =java.util.logging.SimpleFormatter
com.xyz.foo.level = SEVERE
sun.rmi.transport.tcp.logLevel = FINE;

2.4 Logging執行原理

2.4.1 Logger的獲取

首先是調用Logger的如下方法獲得一個logger:

public static synchronized Logger getLogger(String name) {
    LogManager manager =LogManager.getLogManager();
    return manager.demandLogger(name);
}

上面的調用會觸發java.util.logging.LoggerManager的類初始化工作,LoggerManager有一個靜態化初始化塊(這是會先於LoggerManager的構造函數調用的_):

static {  
    AccessController.doPrivileged(newPrivilegedAction<Object>() {  
        public Object run() {  
            String cname =null;  
            try {  
                cname =System.getProperty("java.util.logging.manager");  
                if (cname !=null) {  
                    try {  
                        Class clz =ClassLoader.getSystemClassLoader().loadClass(cname);  
                        manager= (LogManager) clz.newInstance();  
                    } catch(ClassNotFoundException ex) {  
                        Class clz =Thread.currentThread().getContextClassLoader().loadClass(cname);  
                        manager= (LogManager) clz.newInstance();  
                    }  
                }  
            } catch (Exceptionex) {  
                System.err.println("Could not load Logmanager \"" + cname+ "\"");  
                ex.printStackTrace();  
            }  
            if (manager ==null) {
                manager = newLogManager();
            }  
            manager.rootLogger= manager.new RootLogger();
            manager.addLogger(manager.rootLogger);
            Logger.global.setLogManager(manager);
            manager.addLogger(Logger.global);
            return null;  
        }  
    });  
}

從靜態初始化塊中可以看出LoggerManager是可以使用系統屬性java.util.logging.manager指定一個繼承自java.util.logging.LoggerManager的類進行替換的,比如Tomcat啟動腳本中就使用該機制以使用自己的LoggerManager。

不管是JDK默認的java.util.logging.LoggerManager還是自定義的LoggerManager,初始化工作中均會給LoggerManager添加兩個logger,一個是名稱為””的root logger,且logger級別設置為默認的INFO;另一個是名稱為global的全局logger,級別仍然為INFO。

LogManager”類”初始化完成之后就會讀取配置文件(默認為$JAVA_HOME/jre/lib/logging.properties),把配置文件的屬性名的屬性值這樣的鍵值對保存在內存中,方便之后初始化logger的時候使用。

1步驟中Logger類發起的getLogger操作將會調用java.util.logging.LoggerManager的如下方法:

Logger demandLogger(String name) {
    Logger result =getLogger(name);
    if (result == null) {
        result = newLogger(name, null);
        addLogger(result);
        result =getLogger(name);
    }
    return result;
}

可以看出,LoggerManager首先從現有的logger列表中查找,如果找不到的話,會新建一個looger並加入到列表中。當然很重要的是新建looger之后需要對logger進行初始化,這個初始化詳見java.util.logging.LoggerManager#addLogger()方法中,改方法會根據配置文件設置logger的級別以及給logger添加handler等操作。

到此為止logger已經獲取到了,你同時也需要知道此時你的logger中已經有級別、handler等重要信息,下面將分析輸出日志時的邏輯。

2.4.2 日志的輸出

首先我們通常會調用Logger類下面的方法,傳入日志級別以及日志內容。

public void log(Levellevel, String msg) {
    if (level.intValue() < levelValue ||levelValue == offValue) {
        return;
    }
    LogRecord lr = new LogRecord(level, msg);
    doLog(lr);
}

該方法可以看出,Logger類首先是進行級別的校驗,如果級別校驗通過,則會新建一個LogRecord對象,LogRecord中除了日志級別,日志內容之外還會包含調用線程信息,日志時刻等;之后調用doLog(LogRecord lr)方法。

private void doLog(LogRecord lr) {
    lr.setLoggerName(name);
    String ebname =getEffectiveResourceBundleName();
    if (ebname != null) {
        lr.setResourceBundleName(ebname);
        lr.setResourceBundle(findResourceBundle(ebname));
    }
    log(lr);
}

doLog(LogRecord lr)方法中設置了ResourceBundle信息(這個與國際化有關)之后便直接調用log(LogRecord record)方法。

 public void log(LogRecord record) {
        if (record.getLevel().intValue() <levelValue || levelValue == offValue) {
            return;
        }
        synchronized (this) {
            if (filter != null &&!filter.isLoggable(record)) {
                return;
            }
        }
        Logger logger = this;
        while (logger != null) {
            Handler targets[] = logger.getHandlers();
            if(targets != null) {
                for (int i = 0; i < targets.length; i++) {
                    targets[i].publish(record);
                }
            }
            if(!logger.getUseParentHandlers()) {
                break;
            }
            logger= logger.getParent();
        }
    }

很清晰,while循環是重中之重,首先從logger中獲取handler,然后分別調用handler的publish(LogRecordrecord)方法。while循環證明了前面提到的會一直把日志委托給父logger處理的說法,當然也證明了可以使用logger的useParentHandlers屬性控制日志不進行往上層logger傳遞的說法。到此為止logger對日志的控制差不多算是完成,接下來的工作就是看handler的了,這里我們以java.util.logging.ConsoleHandler為例說明日志的輸出。

public class ConsoleHandler extends StreamHandler {
    public ConsoleHandler() {
        sealed = false;
        configure();
        setOutputStream(System.err);
        sealed = true;
    }
}

ConsoleHandler構造函數中除了需要調用自身的configure()方法進行級別、filter、formatter等的設置之外,最重要的我們最關心的是setOutputStream(System.err)這一句,把系統錯誤流作為其輸出。而ConsoleHandler的publish(LogRecordrecord)是繼承自java.util.logging.StreamHandler的,如下所示:

public synchronized void publish(LogRecord record) {
    if(!isLoggable(record)) {
        return;
    }
    String msg;
    try {
        msg =getFormatter().format(record);
    } catch (Exception ex){
        // We don't want tothrow an exception here, but we
        // report theexception to any registered ErrorManager.
        reportError(null,ex, ErrorManager.FORMAT_FAILURE);
        return;
    }       
    try {
        if (!doneHeader) {
            writer.write(getFormatter().getHead(this));
            doneHeader =true;
        }
        writer.write(msg);
    } catch (Exception ex){
        // We don't want tothrow an exception here, but we
        // report theexception to any registered ErrorManager.
        reportError(null,ex, ErrorManager.WRITE_FAILURE);
    }
}

方法邏輯也很清晰,首先是調用Formatter對消息進行格式化,說明一下:格式化其實是進行國際化處理的重要契機。然后直接把消息輸出到對應的輸出流中。需要注意的是handler也會用自己的level和LogRecord中的level進行比較,看是否真正輸出日志。

2.5 解決問題

1.【Main running.】以外的日志為什么沒有輸出?怎么讓它們也能夠出現?
這就是JDK默認的logging.properties文件中配置的handler級別和跟級別均為info導致的,如果希望看到FINE級別日志,需要修改logging.properties文件,同時進行如下兩個修改

java.util.logging.ConsoleHandler.level= FINE//修改
com.bes.logging.level=FINE//添加

2.日志中出現的時間、類名、方法名等是從哪里輸出的?
請參照[java.util.logging.ConsoleHandler.formatter= java.util.logging.SimpleFormatter]配置中指定的java.util.logging.SimpleFormatter類,其public synchronized String format(LogRecord record) 方法說明了一切。

 public synchronized String format(LogRecord record) {
        StringBuffer sb = new StringBuffer();
        // Minimize memory allocations here.
        dat.setTime(record.getMillis());
        args[0] = dat;
        StringBuffer text = new StringBuffer();
        if (formatter == null) {
            formatter = new MessageFormat(format);
        }
        formatter.format(args, text, null);
        sb.append(text);
        sb.append(" ");
        if (record.getSourceClassName() != null) {     
            sb.append(record.getSourceClassName());
        } else {
            sb.append(record.getLoggerName());
        }
        if (record.getSourceMethodName() != null) {
            sb.append(" ");
            sb.append(record.getSourceMethodName());
        }
        sb.append(lineSeparator);
        String message = formatMessage(record);
        sb.append(record.getLevel().getLocalizedName());
        sb.append(": ");
        sb.append(message);
        sb.append(lineSeparator);
        if (record.getThrown() != null) {
            try {
                StringWriter sw = newStringWriter();
                PrintWriter pw = newPrintWriter(sw);
                record.getThrown().printStackTrace(pw);
                pw.close();
                sb.append(sw.toString());
            } catch (Exception ex) {
            }
        }
        return sb.toString();
    }

3.為什么日志就會出現在控制台?
看到java.util.logging.ConsoleHandler 類構造方法中的[setOutputStream(System.err)]語句,相信你已經明白。

4.大型的系統可能有很多子模塊(可簡單理解為有很多包名),如何對這些子模塊進行單獨的日志級別控制?
在logging.properties文件中分別對各個logger的級別進行定義,且最好使用java.util.logging.config.file屬性指定自己的配置文件。

5.apache那個流行的log4j項目和JDK的logging有聯系嗎,怎么實現自己的LoggerManager?
沒聯系,兩個都是日志框架具體實現,兩者的LoggerManager實現邏輯大體一致,只是具體細節不同。

日志框架3:log4j_Cape_sir-CSDN博客

1.簡介

Apache Log4j是當前在J2EE和J2SE開發中用得最多的日志框架(幾乎所有項目都用它),因為它具有出色的性能、靈活的配置以及豐富的功能,並且在業務有特殊的要求時,可以使用自定義組件來代替框架中已有的組件來滿足要求。

基本上所有的大型應用,包括我們常用的框架,比如hibernate;spring;struts等,在其內部都做了一定數量的日志信息。為什么要做這些日志信息,在系統中硬編碼日志記錄信息是調試系統,觀察系統運行狀態的一種方式。可能大部分程序員都還記得自己最開始寫代碼的時候,寫一個方法總是出錯,就喜歡使用System.out.println(“1111111”)之類的代碼來查看程序的運行是否按照自己想要的方式在運行,其實這些sysout就是日志記錄信息,但使用System.out.println或者System.err.println在做日志的時候有其非常大的局限性

1)所有的日志信息輸出都是平行的。比如我不能單獨的控制只輸出某一個或者某幾個模塊里面的日志調試信息;或者我不能控制只輸出某一些日志。

2)日志信息的輸出不能統一控制。比如我不能控制什么時候打印信息,什么時候不打印信息,如果我不想打印任何信息,我只能選擇到所有的java文件中去注釋這些日志信息。

3)日志信息的輸出方式是固定的。比如,在編碼期,就只能輸出到eclipse的控制台上,在tomcat環境中就只能輸出到tomcat的日志文件中,我不能選擇比如輸出日志到數據庫或者輸出日志到Email中等等復雜的控制。

所以,綜上所述,在編碼階段,往往需要一種統一的硬編碼日志方式,來記錄程序的運行狀態,並且能夠提供簡單的方式,統一控制日志的輸出粒度和輸出方式。Log4J就是在這種情況下誕生的,而現在Log4J已經被廣泛的采用,成為了最流行的日志框架之一。

Log4J提供了非常簡單的使用方式,如果不需要深入去發現Log4J,或者需要自己去擴展Log4J,那么使用起來是非常方便的。而Log4J也非常關注性能的問題,因為在應用中大量的采用日志的方式,會帶來一些反面的問題:

1)會使一段代碼的篇幅增大,增加閱讀的難度;
2)增加代碼量,會在一定程度上降低系統運行性能;

而Log4J在性能上不斷的優化,使普通的日志輸出的性能甚至做到比System.out.println更快。最后,Log4J提供了非常方便的擴展方式,能讓我們非常簡單的自定義自己的日志輸出級別,日志輸出方式等。

2.第一個日志示例:簡單使用

public class Log4jTest {
    private static Logger logger = Logger.getLogger(Log4jTest.class);
 
    public static void main(String[] args) {
        // 從字面意思上看非常簡單,我們使用了一個基礎配置器,並調用其configure()方法,即配置方法完成了配置。
        BasicConfigurator.configure();
        logger.debug("my first log4j info");
    }
}

就這么簡單,通過這個例子,其實我們已經演示完了Log4j的使用方式,要把Log4J加入你的應用,只需要這么幾步:

1)導入Log4J的包。
2)完成Log4J的配置。在上面的應用中,我們僅僅是使用了最簡單也最缺乏靈活性的BasicConfigurator來完成配置,后面我們會逐步看到更靈活的配置方式,但是配置總是需要的一個步驟。
3)對於每一個需要日志輸出的類來說,通過Logger.getLogger方法得到一個專屬於這個類的日志記錄器。
4)使用日志記錄器完成日志信息的輸出;在上面的例子中,我們僅僅看到了logger的debug方法,后面會看到更多的日志記錄方式。

3.復雜例子1:日志級別的控制

上面我們看了一個使用Log4J的最簡單的示例,在這個示例里面,我們只在一個類中使用標准的配置打印了一行日志信息。接下來,我們簡單模擬幾個常見的日志情況,來看看Log4J的一些對日志級別的控制能力。

第一個例子,我們來看看Log4J對日志級別的控制。在Log4J中,日志是可以分成不同級別的,這和我們做日志的想法其實是保持一致的。有的時候,我們記錄的信息可能是非常細節的,比如讀取了一個配置文件,我們需要日志這個文件的地址,可能我們要日志這個配置文件里面每一個配置項的詳細信息;而有的時候,我們不需要查看配置文件里面每一個配置項的具體信息,而只想記錄當前已經完成了配置文件的讀取。下面我們就來模擬這樣一個例子:

假設我們的代碼要處理一個數據庫連接的示例,有一個數據庫連接配置文件:db.properties

driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql:///log4j
username=log4j
password=admin

接下來,我們寫一個代碼來讀取和解析這個配置文件:

package cd.itcast.log4j;
import java.io.IOException;
import java.util.Properties;
public class Configure {
    // 同樣,首先得到和這個類綁定的Logger實例
    private static Logger log = Logger.getLogger(Configure.class);
 
    public void config() {
        log.info("using default db.properties");
         config("db.properties");
    }
 
    public void config(String resourceName) {
        log.info("using config file in classpath:" + resourceName);
        try {
            Properties prop = new Properties();
            prop.load(this.getClass().getClassLoader().getResourceAsStream(resourceName));
            log.debug("load properties file success");
            for (String key : prop.stringPropertyNames()) {
                String value = prop.getProperty(key);
                // doSomeConfigWorkUseKeyAndValue(key,value)
                log.debug("[properties] " + key + " : " + value);
            }
        } catch (Exception e) {
            log.error("log properties file failed", e);
        }
    }
}

在改進后的版本中,我們需要注意幾個地方:

1)同樣,我們仍然需要得到一個和該類綁定的Logger實例;
2)我們在該類中並沒有做BasicConfigurator基礎設置,因為Log4J的配置是可以放在外部的,我們的類本身只需要做日志,而不需要管怎么去配置。
3)在該類中,我們一共使用到了三種Logger實例的方法:

  • info:info表示概要信息記錄級別,比如代碼中的正在讀取配置
  • debug:debug表示很詳細的信息記錄級別,比如代碼中的讀取的配置文件內容是什么;
  • error:error表示對錯誤的記錄級別,這里在error方法后面,我們把可能拋出的錯誤作為參數也傳給了方法;

完成該類后,我們寫一個測試來使用這個類:

package cd.itcast.log4j;
import org.apache.log4j.BasicConfigurator;
import org.junit.Test;
public class LogTest2 {
 
    @Test
    public void testLog2(){
        BasicConfigurator.configure();
        Configure conf=new Configure();
        conf.config();
    }
}

同樣,我們先使用BasicConfigurator.config()方法來完成最基本的配置,運行測試,輸出:

0 [main] INFO cd.itcast.log4j.Configure  - using default db.properties
0 [main] INFO cd.itcast.log4j.Configure  - using config file in classpath:db.properties
0 [main] DEBUG cd.itcast.log4j.Configure  - load properties file success
0 [main] DEBUG cd.itcast.log4j.Configure  - [properties] driverClass : com.mysql.jdbc.Driver
0 [main] DEBUG cd.itcast.log4j.Configure  - [properties] url : jdbc:mysql:///log4j
0 [main] DEBUG cd.itcast.log4j.Configure  - [properties] password : admin
0 [main] DEBUG cd.itcast.log4j.Configure  - [properties] username : log4j

可以通過輸出看到,所有的INFO和DEBUG級別的日志信息都輸出來了。接下來,我們先修改代碼,讓代碼報個錯:

package cd.itcast.log4j;
import org.apache.log4j.BasicConfigurator;
import org.junit.Test;
public class LogTest2 {
 
    @Test
    public void testLog2(){
        BasicConfigurator.configure();
        Configure conf=new Configure();
        conf.config("aa.properties");
    }
}

這里,我們故意傳一個不存在的配置文件,運行測試:

0 [main] INFO cd.itcast.log4j.Configure  - using config file in classpath:aa.properties
16 [main] ERROR cd.itcast.log4j.Configure  - log properties file failed
java.lang.NullPointerException
at java.util.Properties$LineReader.readLine(Properties.java:418)
at java.util.Properties.load0(Properties.java:337)
at java.util.Properties.load(Properties.java:325)
at cd.itcast.log4j.Configure.config(Configure.java:20)

可以看到結果里面即打印出了INFO,也打印出了我們想要的ERROR級別,但下面還有錯誤的棧信息,這個就看你自己了,如果不想要錯誤棧信息,只需要打印ERROR級別日志,那么在調用Logger.error方法的時候,不要把錯誤作為第二個參數傳入方法就行了。代碼寫到這里,演示了Log4J中一個非常重要的概念:日志級別。在Log4J中,內置了幾種日志級別,分別是:debug;info;warn;error;和fatal;debug、info和error前面都已經見識過了,下面簡單介紹下warn和fatal:

  • warn代表警告級別,表示需要引起注意,可能程序沒有按照預期運行等等;
  • fatal代表致命級別,表示非常嚴重的錯誤;

通過這幾個級別的解釋,大家應該很容易能夠感覺到,日志級別是有一定的順序的,在Log4J中,日志級別的順序定義為:debug<info<warn<error<fatal;那么這個級別到底有什么用呢?下面接着看這個例子:

假如現在我的應用已經基本測試完成,在客戶那里部署了,我現在不想再看到那么詳細的日志輸出了,我只想看到粗略的步驟即可,即我不想看到DEBUG信息了,那怎么做呢?很簡單,我們只需要在代碼中加入一句話:

package cd.itcast.log4j;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.junit.Test;
public class LogTest2 {
 
    @Test
    public void testLog2(){
        BasicConfigurator.configure();
        Logger.getRootLogger().setLevel(Level.INFO);
        Configure conf=new Configure();
        conf.config();
    }
}

在加入的這一句代碼中,包含着非常重要的2個內容,大家要非常注意。在講解之前,我們先看看輸出的結果:

0 [main] INFO cd.itcast.log4j.Configure  - using default db.properties
0 [main] INFO cd.itcast.log4j.Configure  - using config file in classpath:db.properties

可以看到,這次就只打印出了INFO級別的信息,而沒有打印DEBUG級別了。下面我們來稍微詳細的解釋一下這段代碼。

第一個需要注意的就是,我們在調整打印日志級別的時候,並沒有修改Configure這個類里面的代碼,換句話說,這再次印證了之前我們說了,控制日志級別是應用之外的事情,而不是應用本身代碼控制的。

第二個需要注意的是從字面意義上來看,我們先通過Logger類的getRootLogger方法得到了一個Logger類實例,這個Logger類實例就是根Logger。接着,我們設置了這個根Logger的日志級別為Level.INFO,結合最后輸出的日志,我們很容易想到,我們規定了最小的輸出日志級別是INFO。

這句代碼反應出了兩個非常重要的思想,第一個思想,之前已經提到了,日志級別是有順序的,這里就能明顯看到這個順序對我們控制應用的中的日志級別的作用,根據剛才的順序,我們控制了最低打印級別為Level.INFO,那么應用就只會打印出大於或等於這個級別的日志信息,即info<warn<error<fatal這四個。我們可以通過讓應用報錯,來看看會不會打印出error:

0 [main] INFO cd.itcast.log4j.Configure  - using config file in classpath:aa.properties
0 [main] ERROR cd.itcast.log4j.Configure  - log properties file failed

仍然正常打印出ERROR。第二個思想,代表着Log4J的一個非常重要的體系結構:日志記錄器(Logger)是有層次結構的;並且,這個層次結構和類的結構是保持一致的。我們把上面的示例所涉及的兩個類的結構畫出來:

輸入圖片說明

當我們在LogTest2中使用Logger.getRootLogger()方法的時候,實際上,我們得到了整個應用的根Logger對象,即圖中的rootLogger;而我們在Configure類中寫的:

private static Logger log = Logger.getLogger(Configure.class);

代碼的真正含義是,我在cd.itcast.log4j這個Logger體系結構下創建了一個cd.itcast.log4j.Configure名字的Logger實例。這個實例的根Logger就是在LogTest2中得到的rootLogger();因為LogTest2和Configure是在同一個classLoader之下的,所以他們共享同一個RootLogger。而當我們在rootLogger上設置了日志級別,即在該rootLogger的體系之上設置了一個默認的日志級別,即Level.INFO;而我們並沒有在cd.itcast.log4j.Configure綁定的Logger上設置日志級別,所以他繼承了他的祖先的日志級別,即rootLogger的Level.INFO,所以,打印出來就只有大於或等於INFO的日志級別信息了。關於Logger的體系結構,后面會詳細講到,這里大家只要心理面有一個大概的印象即可。

4.復雜例子2:不同模塊的日志打印

上面我們演示了一個稍微復雜一點的例子,在那個例子中,我們使用了不同的日志打印級別,並且控制了打印級別,但是上面的例子仍然是對於一個類的日志控制,我們在這個例子中,來看看,控制不同模塊的日志打印。

在上面的例子中,我們加入一個新的類,這個類放在一個額外的包中:

package cd.itcast.core;
import org.apache.log4j.Logger;
import cd.itcast.log4j.Configure;
public class LogicProcessor {
    private static Logger log=Logger.getLogger(LogicProcessor.class);
 
    public void init(Configure conf){
        log.info("init logic processor using conf");
    }
 
    public void process(){
        log.info("process some logic");
        log.debug("process some detail logic");
    }
}

在這個類中,我們創建了一個模擬某種核心處理器的類,這個類放在了cd.itcast.core包里面,然后在這個類里面使用不同的日志級別打印了一些日志。

然后我們來完成一個測試:

package cd.itcast.log4j;
import org.apache.log4j.BasicConfigurator;
import org.junit.Test;
import cd.itcast.core.LogicProcessor;
public class LogTest3 {
 
    @Test
    public void testLog(){
        BasicConfigurator.configure();
 
        Configure conf=new Configure();
        conf.config();
 
        LogicProcessor processor=new LogicProcessor();
        processor.init(conf);
        processor.process();
    }
}

注意,這個測試我們仍然是放在cd.itcast.log包中的,這個沒有任何關系。在這個測試中,我們綜合使用到了之前的Configure類和新的LogicProcessor類來完成一些業務邏輯。同樣,我們先簡單使用BasicConfigurator.configure()完成基礎設置。運行測試,輸出:

0 [main] INFO cd.itcast.log4j.Configure  - using default db.properties
0 [main] INFO cd.itcast.log4j.Configure  - using config file in classpath:db.properties
15 [main] DEBUG cd.itcast.log4j.Configure  - load properties file success
31 [main] DEBUG cd.itcast.log4j.Configure  - [properties] driverClass : com.mysql.jdbc.Driver
31 [main] DEBUG cd.itcast.log4j.Configure  - [properties] url : jdbc:mysql:///log4j
31 [main] DEBUG cd.itcast.log4j.Configure  - [properties] password : admin
31 [main] DEBUG cd.itcast.log4j.Configure  - [properties] username : log4j
31 [main] INFO cd.itcast.core.LogicProcessor  - init logic processor using conf
31 [main] INFO cd.itcast.core.LogicProcessor  - process some logic
31 [main] DEBUG cd.itcast.core.LogicProcessor  - process some detail logic

觀察輸出,兩個類里面的所有的日志都輸出了,根據上一個例子,我們基本能夠猜到,默認情況下,rootLogger的日志級別被設置為了Level.DEBUG。下面,我們來模擬一個場景。假如現在我們已經完成了cd.itcast.log.Configure類的詳細測試,我們只想這個類做WARN級別之上的日志輸出,但是現在我們還沒有確定LogicProcessor是否正常運行,所以對於LogicProcessor類我們要做DEBUG級別的日志。

怎么控制這個輸出?其實我們根據前一個例子解釋的Logger的繼承的體系結構,我們能有兩種方式來完成這個目的。
(1)設置rootLogger日志級別為DEBUG;設置cd.itcast.log4j.Configure日志級別為WARN。
(2)設置rootLogger日志級別為WARN;設置cd.itcast.core.LogicProcessor日志級別為DEBUG。
我們隨意選擇一種模式,比如第二種,那么,修改我們的代碼:

import org.junit.Test;
import cd.itcast.core.LogicProcessor;
public class LogTest3 {
    @Test
    public void testLog(){
        BasicConfigurator.configure();
        Logger.getRootLogger().setLevel(Level.WARN);
        Logger.getLogger("cd.itcast.core.LogicProcessor").setLevel(Level.DEBUG);
 
        Configure conf=new Configure();
        conf.config();
 
        LogicProcessor processor=new LogicProcessor();
        processor.init(conf);
        processor.process();
    }
}

再次運行測試,輸出:

0 [main] INFO cd.itcast.core.LogicProcessor  - init logic processor using conf
0 [main] INFO cd.itcast.core.LogicProcessor  - process some logic
0 [main] DEBUG cd.itcast.core.LogicProcessor  - process some detail logic

達到我們的目的。我們回過頭來看看我們到底做了些什么。加了兩行代碼,第一行代碼大家應該熟悉了,設置rootLogger的日志級別為WARN,那么在該rootLogger體系結構中的cd.itcast.log.Configure和cd.itcast.core.LogicProcessor默認日志級別都變成了WARN。接着第二行代碼,我們使用Logger.getLogger()方法,傳入cd.itcast.core.LogicProcessor參數,就得到了和LogicProcessor類綁定的那個Logger實例。注意這里,通過Logger.getLogger(“cd.itcast.core.LogicProcessor”)得到的Logger實例和我們在LogicProcessor類中使用Logger.getLogger(LogicProcessor.class)得到的Logger實例是一個實例。換句話說,在一個rootLogger體系結構中,綁定到同一個名字的Logger實例只會有一份。接着,我們再設置這個Logger的日志記錄級別為Level.DEBUG,那么LogicProcessor綁定的Logger實例就不再繼承rootLogger的日志記錄級別了。所以,能正常打印。

通過這段代碼,我們可以再次看到這個rootLogger的繼承體系結構和繼承的關系。當然,Log4J遠遠不止如此。

假如現在我們在cd.itcast.log包中和cd.itcast.core包中,不止一個類,換句話說,我現在想讓cd.itcast.log包及其子包中的類的日志級別為WARN,而讓cd.itcast.core包及其子包中的所有類的日志級別為DEBUG,又該怎么做呢?可能這種情況才是我們在現實應用當中會遇到的吧。其實非常簡單,我們只需要改一句代碼:

@Test
public void testLog(){
    BasicConfigurator.configure();
    Logger.getRootLogger().setLevel(Level.WARN);
    Logger.getLogger("cd.itcast.core").setLevel(Level.DEBUG);
 
    Configure conf=new Configure();
    conf.config();
 
    LogicProcessor processor=new LogicProcessor();
    processor.init(conf);
    processor.process();
}

再次運行測試,輸出:

0 [main] INFO cd.itcast.core.LogicProcessor  - init logic processor using conf
0 [main] INFO cd.itcast.core.LogicProcessor  - process some logic
0 [main] DEBUG cd.itcast.core.LogicProcessor  - process some detail logic

我們這次不再得到cd.itcast.core.LogicProcessor綁定的Logger,而是得到cd.itcast.core包對應的Logger,是的,就是這樣。當我們綁定cd.itcast.core.LogicProcessor對應的Logger的時候,實際上隱式的綁定了cd、cd.itcast、cd.itcast.core這三個Logger。所以,現在的rootLogger體系結構應該是這樣:

輸入圖片說明

那么,當我設置rootLogger的日志級別為WARN,並得到cd.itcast.core綁定的Logger,設置其日志級別為DEBUG的時候,rootLogger、cd、cd.itcast、cd.itcast.log、cd.itcast.log.Configure的日志級別都是WARN,而cd.itcast.core、cd.itcast.core.LogicProcessor的日志級別為DEBUG。

通過這個示例,我們對Logger的繼承體系應該有了更深入一些的了解,也可以通過這個例子可以看出,當我們對不同級別的Logger做特定的配置的時候,會非常靈活的控制我整個系統的日志輸出,這是Log4J最為強大的地方之一。

如果你曾經使用過Log4j,你也許會對上面的代碼感到非常奇怪,以前不是這樣用Log4J的呀,也不要慌,之后就能慢慢看到平時我們使用Log4J的方式和其使用方式后面的真正含義。

5.Log4j體系結構

當我們在描述為系統做日志這個動作的時候,實際上描述了3個點;類似於小學語文學語法一樣。做日志,其實就是在規定,在什么地方用日志記錄器以什么樣的格式做日志。把三個最重要的點抽取出來,即什么地方,日志記錄器,什么格式。在Log4J中,就使用了三個最重要的組件來描述這三個要素,即:

Logger:日志記錄器

Appender:什么地方

Layout:什么格式

5.1 Logger的結構

之前的示例中大家對Logger已經有了一定的認識,Logger就是最重要的,我們直接用來完成日志的工具。使用日志框架一個非常重要的目的就是讓程序員能夠方便的控制特定的日志的輸出。要能夠控制特定日志的輸出,就需要創建一種特殊的結構來划分我們的日志記錄器。而對於JAVA程序員來說,按照包的層次關系划分Logger是最容易想到,也最貼近我們的代碼結構的。

之前我們在創建Logger的時候,都是使用Logger.getLogger(Class)方法來得到一個類綁定的日志記錄器的。實際上,當我們說把一個日志記錄器綁定在一個類上,這種說法是不准確的,正確的說,我們僅僅是使用給定的類的全限定名為Logger取了一個名字。這里請大家注意一下,Logger都是有名字的,假如我們除開rootLogger不談,我們可以把Logger想象成一張表里面的數據,Logger對應的名字就是其主鍵,當兩個Logger的名字相同,這兩個Logger就是同一個Logger實例。我們可以簡單的通過一個實例來驗證:

@Test
public void testLoggerName(){
    BasicConfigurator.configure();
    Logger log1=Logger.getLogger("a");
    Logger log2=Logger.getLogger("a");
    log1.info(log1==log2);
}

控制台打印:

0 [main] INFO a  - true;

說明Logger自身維護着每一個名字的Logger實例的引用,保證相同名字的Logger在不同地方獲取到的實例是一致的,這樣就允許我們在統一的代碼中配置不同Logger的特性。

另外,Logger的層次結構,也是靠Logger的名字來區分的,比如:名稱為java的Logger就是java.util的父Logger;java.util是java.util.Collection的父Logger;Logger的體系結構和package的結構划分類似,使用.來區分;所以我們前面才說,使用類的全限定名是最簡單,也是最符合logger的體系結構的命名方式。當然,你也可能使用任何的能想到的方式去處理Logger的命名;這也是可以的。

看到這里,可能有的人會提出疑問,那既然Logger的層次結構是按照Logger的名字來創建的,那在創建Logger的時候,是否必須按照其結構來順序創建Logger?比如:

Logger log1=Logger.getLogger(“cd”);
Logger log2=Logger.getLogger(“cd.itcast”);
Logger log3=Logger.getLogger(“cd.itcast.log”);

是否必須要按照這個順序創建呢?不需要。在做前面的示例的時候,我們說到,大家可以認為當我們通過Logger.getLogger(“cd.itcast.log”)的時候,Log4J其實為我們創建了cd;cd.itcast和cd.itcast.log這三個Logger;其實不然,如果是這樣的話,Log4J可能會產生非常多不必要的Logger。所以,真正的方式應該是,當我通過Logger.getLogger(“cd.itcast.log”)的時候,Log4J僅僅為我們創建了cd.itcast.log這個Logger;而當我們再次使用Logget.getLogger(“cd.itcast”)的時候,Log4J才會為我們創建cd.itcast這個Logger,並且Log4J會自動的去尋找這個Logger的上下級關系,並自動的把這個新創建的Logger添加到已有的Logger結構體系中。

上面說到,除了rootLogger之外,其他的Logger都有名字,那么rootLogger呢?rootLogger有下面的規范:

rootLogger是沒有名字的;

rootLogger是自動存在的;

rootLogger必須設置Level等級;

rootLogger沒有名字,所以無法像其他的類一樣,可以通過Logger.getLogger(String)得到,他只能通過Logger.getRootLogger方法得到;而我們前面使用的Logger.getLogger(Class)方法,其實相當於Logger.getLogger(Class.getName())而已;所以真正賦予Logger的是一個名字,而不是類型;

在Logger中,非常重要的一個組件,就是Logger的日志級別,即Level。關於Level的划分,使用,我們在前面的例子中已經大概了解到了,下面來看看完整的Level的定義和其在Logger體系中的繼承方式,這是一個很簡單的概念。

首先,在Log4J中,為我們默認的定義了7種不同的Level級別,即all<debug<info<warn<error<fatal<off;而這7中Level級別又剛好對應着Level類中的7個默認實例:Level.ALL;Level.DEBUG;Level.INFO;Level.WARN;Level.ERROR;Level.FATAL和Level.OFF。我們在之前也只看到了其中的debug到fatal這5種;這五種日志級別正好對應着Logger中的五個方法,即:

public class Logger {
    // 輸出日志方法:
    public void debug(Object message);
    public void info(Object message);
    public void warn(Object message);
    public void error(Object message);
    public void fatal(Object message);
    // 輸出帶有錯誤的日志方法:
    public void debug(Object message, Throwable t);
    public void info(Object message, Throwable t);
    public void warn(Object message, Throwable t);
    public void error(Object message, Throwable t);
    public void fatal(Object message, Throwable t);
    // 更通用的輸出日志方法:
    public void log(Level p, Object message);
}

為什么這里列出了11個方法,其實大家仔細看一下就知道了,最上面5個方法和中間的5個方法其實是對應的,只是中間的5個方法都帶有對應的錯誤信息。而更下面的log方法,是允許我們直接使用Level類型來輸出日志,這給了我們直接使用自定義的Level級別提供了輸出日志的方式。換句話說,debug(Object message)其實就是log(Level.DEBUG,message)的簡寫而已。

要能輸出日志,按照常規來說,每一個Logger實例都應該設置其對應的Level;但是如果這樣做,會讓我們控制Logger的Level非常麻煩,而Log4J借助Logger的層級關系,使用繼承的方式來最大限度的減少Level的設置。這個規律我們在之前的代碼中已經講過。

一種不太常用的方式是使用Threshold方式來限制日志輸出。Threshold我們可以理解為門檻,它和Level的概念完全一致,只是使用方式有一些區別,先來看代碼:

@Test
public void testLogLevel2() {
    BasicConfigurator.configure();
 
    Logger logger = Logger.getLogger("cd.itcast");
    logger.getLoggerRepository().setThreshold(Level.WARN);
    Logger barLogger = Logger.getLogger("cd.itcast.log");
 
    logger.warn("logger warn");
    logger.debug("logger debug");
    barLogger.info("bar logger info");
    barLogger.debug("bar logger debug");
}

我們先創建了一個名字為cd.itcast的Logger,這次我們並沒有設置其Level,而是使用logger.getLoggerRepository()方法得到了一個LoggerRepository對象。這個LoggerRepository可以理解為Logger棧,簡單說,就是從當前Logger開始其下的所有的子Logger。然后,我們設置了這個棧的日志門檻,Threshold就是門檻的意思,換句話說就是最低日志輸出級別為WARN,那么其下的所有的Logger的最低日志輸出級別就變為了WARN。所以,這段代碼執行的結果是:

0 [main] WARN cd.itcast  - logger warn

Threshold優先級>Level優先級,這里需要注意一點,Threshold優先級大於Level優先級,不等於Level就沒有了意義。假如Threshold設置的級別為DEBUG,而Level設置的等級為INFO,那么最終,logger.debug還是不會輸出日志。

Threshold的方式和Level一樣,如果子Logger設置了自己的Threshold,則會使用自己的Threshold,如果沒有設置,則繼續向上查詢,直到找到一個父類的Threshold或者rootLogger的level。只要父Logger的Threshold設置好了,子Logger的Level也失效了。

上面簡單的介紹了Logger和Level的意義和使用方式,其實,在真正使用Log4J的時候,我們一般都不需要使用代碼的方式去配置Level或者Threshold,這些配置更多的時候是使用配置文件來完成的,這個后面會詳細介紹。但是,不管使用什么樣的配置文件,最終也會解釋成這樣的配置代碼,所以,理解了這些代碼,再去使用配置文件,會更加清楚到底配置文件在干什么,同樣,為我們自己去擴展Log4J的配置,想要自己去實現自定義的配置文件格式,提供了可能。

5.2 Appender的結構

使用Logger的日志記錄方法,僅僅是發出了日志記錄的事件,具體日志要記錄到什么地方,需要Appender的支持。在Log4J中,Appender定義了日志輸出的目的地。在上面所有的示例當中,我們日志輸出的目的地都是控制台,在Log4j中,還有非常多的Appender可供選擇,可以將日志輸出到文件,網絡,數據庫等等,這個后面再介紹。說到這里,可能有人就已經會思考,既然Logger對象的info()等方法僅僅是發出了日志記錄的事件,還需要指定輸出目的地;那么我們之前的示例代碼也並沒有為任何一個Logger設置Appender啊?其實這很好理解,我們回顧一下之前的Level,按道理,也應該為每一個Logger指定對應的日志輸出級別,但是我們也並沒有這樣做,正是因為Logger本身存在一個完整的體系結構,而Level能夠在這個結構中自下而上的繼承。同理,Appender也具有這種繼承的特性。下面給出一段代碼,先看看怎么使用代碼的方式指定Appender:

@Test
public void testLogAppender1(){
    Logger log1=Logger.getLogger("cd");
    log1.setLevel(Level.DEBUG);
    log1.addAppender(new ConsoleAppender(new SimpleLayout()));
    Logger log2=Logger.getLogger("cd.itcast.log");
    log2.info("log2 info");
    log2.debug("log2 debug");
}

注意在這段代碼中的加粗的代碼。第一句代碼設置了日志級別為DEBUG;第二條代碼,調用了Logger的addAppender 方法添加了一個ConsoleAppender;從類的名字上看就知道這是一個把日志輸出到控制台上的Appender,在創建ConsoleAppender的時候,又傳入了一個SimpleLayout的實例;關於Layout下面再介紹,現在只需要關注Appender的繼承特性。接下來,又創建了一個cd.itcast.log的子Logger;並且使用這個Logger輸出了兩條日志信息。運行測試,輸出:

INFO - log2 info
DEBUG - log2 debug

請注意這個輸出,很明顯已經和之前的輸出信息的格式完全不一樣了,這里的輸出格式就是由我們在cd這個Logger上面設置的ConsoleAppender+SimpleLayout所規定的。從這個例子中,我們可以看到,我們改變了cd這個Logger的Appender;他下面的子Logger自然就繼承了這個Appender,輸出了另外一種格式的信息。從這段代碼中,我們能看出Logger的繼承性,假如我們把代碼修改為以下這樣:

@Test
public void testLogAppender1(){
    BasicConfigurator.configure();
    Logger log1=Logger.getLogger("cd");
    log1.setLevel(Level.DEBUG);
    log1.addAppender(new ConsoleAppender(new SimpleLayout()));
    Logger log2=Logger.getLogger("cd.itcast.log");
    log2.info("log2 info");
    log2.debug("log2 debug");
}

在這段代碼中,我們僅僅只是添加了BasicConfigurator來完成一個基本的配置。我們先來分析下這段代碼。首先我們完成了基本的配置,從前面的測試代碼中,我們可以知道,這條代碼為rootLogger設置了一個DEBUG的Level;另外,現在我們知道了,這條代碼肯定還為rootLogger添加了一個ConsoleAppender。然后我們創建了名字為cd的Logger,並且另外添加了一個Appender;接着又創建了名字為cd.itcast.log的Logger,最后使用這個Logger輸出了兩條日志信息。根據前面的Level的表現,我們猜想,當使用cd.itcast.log這個Logger做日志的時候,因為這個Logger本身是沒有添加任何Appender,所以他會向上查詢任何一個添加了Appender的父Logger,即找到了cd這個Logger,最后使用cd這個Logger完成日志,那么我們預計的結果是這段代碼和上一段代碼輸出相同。

我們來運行一下這段代碼,輸出:

INFO - log2 info
0 [main] INFO cd.itcast.log  - log2 info
DEBUG - log2 debug
0 [main] DEBUG cd.itcast.log  - log2 debug

和我們預測的結果不一樣。log2 info和log2 debug分別被輸出了兩次。我們觀察結果,兩條日志的輸出都是先有一條ConsoleAppender+SimpleLayout的方式輸出的然后緊跟一條使用BasicConfigurator的輸出方式。那我們就能大膽的猜測了,Logger上的Appender不光能繼承其父Logger上的Appender,更重要的是,他不光只繼承一個,而是只要是其父Logger,其上指定的Appender都會追加到這個子Logger之上。所以,這個例子中,cd.itcast.log這個Logger不光繼承了cd這個Logger上的Appender,還得到了rootLogger上的Appender;所以輸出了這樣的結果。在Log4J中,這個特性叫做Appender的追加性。默認情況下,所有的Logger都自動具有追加性,通過一個表來說明:

輸入圖片說明

但是,在某些情況下,這樣做反而會引起日志輸出的混亂。有些時候,我們並不希望Logger具有追加性。比如在上面這張表中,我們想讓cd.itcast.log只需要繼承A2和自己的A3Appender,而不想使用root上面的A1 Appender,又該怎么做呢?

其實很簡單,在Logger上,都有一個setAdditivity方法,如果設置setAdditivity為false,則該logger的子類停止追加該logger之上的Appender;如果設置為true,則具有追加性。修改一下上表:

輸入圖片說明

再來一段代碼看看是否如此:

@Test
public void testLogAppender2() throws Exception{
    BasicConfigurator.configure();
    Logger log1=Logger.getLogger("cd");
    log1.setAdditivity(false);
    log1.addAppender(new ConsoleAppender(new SimpleLayout()));
 
    Logger log2=Logger.getLogger("cd.itcast");
    log2.addAppender(new FileAppender(new SimpleLayout(),"a0.log"));
 
    Logger log3=Logger.getLogger("cd.itcast.log");
    log3.info("log2 info");
}

先來分析這段代碼,在這段代碼中有一些新的知識,簡單理解即可。首先,我們使用BasicConfigurator.configure()方法配置了rootLogger;接着定義了名稱為cd的Logger;並為其添加了一個ConsoleAppender,但是這里,我們這里設置了additivity為false,即cd和cd之后的logger都不會再添加rootLogger的Appender了。接下來,我們創建了cd.itcast這個Logger,並且為這個Logger指定了一個FileAppender。FileAppender很簡單,除了同樣要指定一個Layout,這個在后面介紹,第二個參數還需要指定輸出日志的名稱;最后,我們創建了cd.itcast.log,並使用這個Logger輸出日志。按照上面的表所展示的規律,因為cd.itcast.log沒有指定任何的Appender,所以向上查詢。找到cd.itcast,cd.itcast.log得到其上的FileAppender,因為cd.itcast沒有設置additivity,默認為true,繼續向上查找,cd.itcast.log會得到cd的ConsoleAppender;但是因為cd設置了additivity為false,所以不再向上查詢,最后,cd.itcast.log會向FileAppender和ConsoleAppender輸出日志。

運行測試,結果:

INFO - log2 info

並且在應用下增加一個a0.log文件,內容為INFO - log2 info。符合我們的預期。

在Log4J中,一個Logger可以添加多個Appender,不管是通過繼承的方式還是通過調用Logger.addAppender方法添加。只要添加到了某一個Logger之上,在這個Logger之上的任何一個可以被輸出的日志都會分別輸出到所有的Appender之上。

5.3 docLayout的結構

Logger規定了輸出什么日志,Appender規定了日志輸出到哪里,當然,我們還會奢望,以什么樣的方式輸出日志。這就涉及到之前我們在觀察Appender的時候創建ConsoleAppender和FileAppender都需要傳入的Layout。在Log4J中,Layout對象提供了以什么樣的方式格式化日志。這個對象是綁定在Appender之上的,一般在Appender創建的時候指定。

下面就簡單看一個最常使用的Layout:PatternLayout。PatternLayout允許使用標准的輸出格式來指定格式化日志消息的樣式。舉個簡單的例子,可能之前大家看到的使用BasicConfigurator配置的rootLogger輸出的日志樣式和我們使用ConsoleAppender(new SimpleLayout)創建的輸出樣式完全不一樣。那大抵類似:

0 [main] INFO cd.itcast.core.LogicProcessor  - process some logic

那這樣的日志輸出樣式是什么樣子的呢?一個代碼:

@Test
public void testPatternLayout(){
    Logger log=Logger.getLogger("cd");
    String pattern="%r [%t] %-5p %c - %m%n";
    log.addAppender(new ConsoleAppender(new PatternLayout(pattern)));
    Logger log2=Logger.getLogger("cd.itcast.log");
    log2.info("log2 info");
}

運行測試輸出:

0 [main] INFO  cd.itcast.log - log2 info

符合我們的預期。注意粗體字。首先定義了一個格式化日志的模式,在這個模式中,有很多以%開頭的參數,每一個特定的參數代表着一種日志的內容。比如%r代表從Log4j啟動到運行這條日志的時間差,單位為毫秒;第二個參數%t代表運行該日志的線程名稱;第三個參數%-5p,首先-5代表這個字符總占用5個位置,p代表日志輸出級別;%c代表輸出日志的Logger的名字;%m代表輸出的日志內容;%n代表分行。可以看到,其實PatternLayout的使用是非常簡單的,只需要了解所有內置的參數和其表示的含義,再按照想要的方式輸出即可。具體日志格式化參數,請參見Properties配置文件。

5.4 三個組件的使用

前面簡單的了解了Log4J中最重要的3個組件,下面我們來看看Log4j是怎么使用這3個組件完成當我們調用logger.debug()方法能在控制台上打印出日志信息的。

第一步,繼承數體系上的門檻檢查:首先當調用info()方法后,Log4J會立刻使用該Logger所在的體系結構中設置的門檻去檢查當前日志的級別。如果級別不夠,立刻作廢當前日志請求。
第二步,Level級別檢查:使用當前Logger上設置的或者繼承的Level級別來檢查當前的日志級別。如果當前日志級別不夠,立刻作廢當前日志請求。
第三步,創建LoggingEvent對象:當日志審核通過,Log4J就會創建一個LoggingEvent對象(即日志事件對象)。在該對象中,會保存和本次日志相關的所有參數信息,包括日志內容,日志時間等。
第四步,執行Appender:當創建完成LoggingEvent對象時候,會該對象交給當前logger上起作用的所有的Appender對象,並調用這些對象的doAppend方法來處理日志消息。
第五步,格式化日志消息:接下來,會使用每一個Appender綁定的Layout對象(如果有)來格式化日志消息。Layout對象會把LoggingEvent格式化成最終准備輸出的String。
第六步,輸出日志消息:當得到最終要輸出的String對象之后,appender會把字符輸出到最終的目標上,比如控制台或者文件。

三個主要組件的組成結構:
輸入圖片說明

日志執行流程圖:
輸入圖片說明

日志類結構圖:
輸入圖片說明

5.5 Log4j的配置文件

通過前面的示例,我們已經對Log4j的使用方式有了一定的了解。我們再返回去看我們第二個稍微復雜的例子。單看Configure和LogicProcessor兩個類,從代碼的角度來看,在這兩個類中硬編碼日志,是沒有問題的,也是沒法優化的,比如在代碼中添加log.info(string),這種代碼是必要的;在這種情況下,我應用中所有的類必要引入的也就只是一個Logger類,引入這個復雜性也是我們能夠控制的。但是最重要的注意力,我們來思考,在真正一個應用當中,我們的測試類又該怎么表現呢?通過最開始的示例代碼,我們已經知道,要正常的運行Log4J的日志功能,必須要至少對rootLogger進行相關的配,之前隨着我們的代碼的復雜度越來越高,我們發現,要能夠控制我們的日志的輸出級別,各個模塊的日志輸出控制,我們必須要在測試代碼中加入大量的Log4J的代碼;比如設定Level,Threshold,Appender,Layout,Pattern等等代碼,換句話說,如果這些代碼都必須硬編碼到程序中,我們必須要在我們的應用啟動的時候就執行這些代碼,比如在WEB環境中,就要使用ServletContextListener等來初始化Log4J環境,或者在我們的桌面應用的啟動過程中,使用這些代碼來控制Log4J的初始化。這樣做的后果是,雖然我們也能達到統一控制日志輸出的目的,但是我們仍然需要使用大量的代碼來控制這些內容;也沒法達到一種靈活統一配置的方式,因為我們知道,在大部分的情況下,使用配置文件的方式肯定優於代碼的配置方式。

慶幸的是,Log4J遠遠不止能使用代碼來完成配置,他還提供了更多更靈活的配置方式,比如我們接下來要介紹的properties和XML的配置。

6 Log4j架構分析

6.1 組件介紹

1)Logger:負責供客戶端代碼調用,執行debug(Object msg)、info(Object msg)、warn(Object msg)、error(Object msg)等方法。
輸入圖片說明

Logger 繼承自 Category,Logger有三個子類:

  • org.apache.log4j.spi.RootLogger。這是默認的Logger類。
  • org.apache.log4j.spi.NOPLogger。這個類針對日志的輸出不做任何操作,直接丟棄。
  • org.apache.log4j.spi.RootCategory。此類已經不推薦使用,用RootLogger代替。

Logger 一共有如下日志級別:

  • DEBUG
  • INFO
  • WARN
  • ERROR
  • FATAL

另外,還有兩個特殊的日志級別:

  • ALL 所有的日志輸出,不論其級別。
  • OFF 所有日志一律不輸出,與ALL相反。

2)Appender:負責日志的輸出,Log4j已經實現了多種不同目標的輸出方式,可以向文件輸出日志、向控制台輸出日志、向Socket輸出日志等。當前Log4j中Appender的子類實現及其層次結構如下:
輸入圖片說明
在這里插入圖片描述
3)Layout:負責日志信息的格式化。
在這里插入圖片描述

已經實現的類有:

輸入圖片說明輸入圖片說明在這里插入圖片描述

6.2 執行順序及關系

調用Log4j輸出日志時,調用各個組件的順序如下圖所示:

1)日志信息傳入 Logger。
2)將日志信息封裝成 LoggingEvent 對象並傳入 Appender。
3)在 Appender 中調用 Filter 對日志信息進行過濾,調用 Layout 對日志信息進行格式化,然后輸出。
輸入圖片說明

6.3 Log4j初始化

下面分三個步驟來介紹Log4j的初始化:

getLogger(String):在代碼中我們以如下的代碼來使用Log4j,如下:

private Logger _logger = Logger.getLogger(Hello.class);

1)在執行Logger.getLogger(Class)方法,在Log4j內部會執行一系列方法。其執行序列圖如下所示: 輸入圖片說明

2)LogManager初始化:如果是第一次調用LogManager,這時Log4j會讀取配置文件並對自身初始化。其執行序列圖如下: 輸入圖片說明

其中:

  • Hierarchy 實現了 LoggerRepository 接口。
  • RootLogger 實現了 Logger 接口。
  • DefaultRepositorySelector 實現了 RepositorySelector 接口。
  • getSystemProperty(String key, String def)) 這一步驟中獲取系統變量log4j.defaultInitOverride、log4j.configuration、log4j.configuratorClass的值。log4j已經不推薦設置這些系統變
  • getResource(String) 這一步驟中獲取log4j.xml、log4j.properties配置文件。首先獲取log4j.xml,然后再獲取log4j.properties,如果在log4j.xml 和log4j.properties 都獲取失敗的情況下會用 log4j.configuration來代替(如果log4j.configration存在的話)。
  • selectAndConfigure 這一步驟中,如果class 為空,並且url 指向的文件名后綴為xml,則將class設置為“org.apache.log4j.xml.DOMConfigurator”,否則新建一個“org.apache.log4j.PropertyConfigurator”的實例。

3)解析配置文件:獲得一個Configurator實例后,調用它的 doConfigure(Url url, LoggerRepository repository)方法傳入配置文件完事路徑,然后解析配置文件內容。下面以 log4j.xml 為例來介紹配置文件的解析過程:
在這里插入圖片描述

4)在執行 doConfigure(ParseAction action, LoggerRepository repository) 方法時,會通過獲取系統的環境變量 javax.xml.parsers.DocumentBuilderFactory 獲得XML的Document解析器對配置文件log4j.xml進行解析。

5)parse(Element element)的關鍵代碼如下。

String tagName = null;
Element currentElement = null;
Node currentNode = null;
NodeList children = element.getChildNodes();
final int length = children.getLength();
for (int loop = 0; loop < length; loop++) {
    currentNode = children.item(loop);
    if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
        currentElement = (Element) currentNode;
        tagName = currentElement.getTagName();
        if (tagName.equals(CATEGORY_FACTORY_TAG)
            || tagName.equals(LOGGER_FACTORY_TAG)) {
            parseCategoryFactory(currentElement);
        }
    }
}
for (int loop = 0; loop < length; loop++) {
    currentNode = children.item(loop);
    if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
        currentElement = (Element) currentNode;
        tagName = currentElement.getTagName();
        if (tagName.equals(CATEGORY) || tagName.equals(LOGGER)) {
            parseCategory(currentElement);
        } else if (tagName.equals(ROOT_TAG)) {
            parseRoot(currentElement);
        } else if (tagName.equals(RENDERER_TAG)) {
            parseRenderer(currentElement);
        } else if (tagName.equals(THROWABLE_RENDERER_TAG)) {
        if (repository instanceof ThrowableRendererSupport) {
            ThrowableRenderer tr = parseThrowableRenderer(currentElement);
            if (tr != null) {
                ((ThrowableRendererSupport) repository)
                    .setThrowableRenderer(tr);
            }
        }
    } else if (!(tagName.equals(APPENDER_TAG)
        || tagName.equals(CATEGORY_FACTORY_TAG) || tagName
        .equals(LOGGER_FACTORY_TAG))) {
        quietParseUnrecognizedElement(repository, currentElement,
          props);
    }
}
  • 1.獲取根元素的 tagName。
    1)如果 tagName 不是 log4j:configuration 並且 不是已經不推薦的 configuration,輸出錯誤信息。
    2)如果 tagName 不是 log4j:configuration 是已經不推薦的 configuration,輸出警告信息。

  • 2.獲取根元素的 debug 屬性。
    1)如果 debug 屬性的值不等於"“且不等於"null”,調用 LogLog.setInternalDebugging(boolean),否則輸出調試信息。

  • 3.獲取根元素的 reset 屬性。
    1)如果 reset 屬性的值 不等於"“且等於"true”,調用 repository.resetConfiguration()。

  • 4.獲取根元素的 threshold 屬性。
    1)如果 threshold 屬性不等於"“且不等於"null”,調用 repository.setThreshold(String)。

  • 5.循環處理根元素的所有子元素。
    1)如果子元素是 categoryFactory 或 loggerFactory,則調用內部方法 parseCategoryFactory(Element factoryElement) 處理;

  • 6.再次循環處理根元素的所有子元素。
    1)如果子元素是 category 或 logger,調用內部方法 parseCategory(Element loggerElement) 處理。
    2)如果子元素是 root,調用內部方法 parseRoot(Element rootElement) 處理。
    3)如果子元素是 renderer,調用內部方法 parseRenderer(Element rootElement) 處理。
    4)如果子元素是 throwableRenderer 並且repository instanceof ThrowableRendererSupport,調用內部方法 parseThrowableRenderer(Element rootElement) 處理返回一個ThrowableRenderer,如果沒有返回null,調用((ThrowableRendererSupport) repository).setThrowableRenderer(ThrowableRenderer)。
    5)如果子元素是 appender 或者 categoryFactory 或者 loggerFactory,調用內部方法 quietParseUnrecognizedElement(Object instance, Element element, Properties props) 處理。

6.4 Log4j輸出日志

Log4j輸出日志分為六個步驟:全局開關控制、日志等級過濾、封裝日志信息、過濾器處理、日志信息格式化、輸出至文件。下面分兩個環節來介紹這六個步驟是如何實現的:

第一環節:預處理,當調用Log4j的方法(如:debug(String, Throwable)、info(String, Throwable))輸出日志時,首先對日志信息進行預處理,其序列圖如下; 輸入圖片說明

說明:

1)isDisabled(int level):根據全局日志等級threshold進行判斷,如果日志等級低於threshold,不輸出日志。
2)isGreaterOrEquals(Priority r):根據當前logger配置的日志等級level進行判斷,如果日志等級低於當前logger配置的日志等級,不輸出日志。
3)foredLog(String fqcn Priority level, Object message, Throwable t):將日志信息封裝成LoggingEvent對象。
4)callAppenders(LoggingEvent event):將LoggingEvent對象分發給所有的Appender。其實現代碼如下:

public void callAppenders(LoggingEvent event) {
    int writes = 0;
 
    for (Category c = this; c != null; c = c.parent) {
        // Protected against simultaneous call to addAppender,
        // removeAppender,...
        synchronized (c) {
            if (c.aai != null) {
                writes += c.aai.appendLoopOnAppenders(event);
            }
            if (!c.additive) {
                break;
            }
        }
    }
 
    if (writes == 0) {
        repository.emitNoAppenderWarning(this);
    }
}
    public int appendLoopOnAppenders(LoggingEvent event) {
    int size = 0;
    Appender appender;
 
    if (appenderList != null) {
        size = appenderList.size();
        for (int i = 0; i < size; i++) {
            appender = (Appender) appenderList.elementAt(i);
            appender.doAppend(event);
        }
    }
    return size;
}

第二環節:輸出日志。輸出日志前還有兩道工序需要處理:Filter處理和日志信息格式化。其執行序列圖如下: 輸入圖片說明

相應的代碼如下:

public synchronized void doAppend(LoggingEvent event) {
    if (closed) {
        LogLog.error("Attempted to append to closed appender named ["
                + name + "].");
        return;
    }
 
    if (!isAsSevereAsThreshold(event.getLevel())) {
        return;
    }
 
    Filter f = this.headFilter;
 
    FILTER_LOOP: while (f != null) {
        switch (f.decide(event)) {
 
        case Filter.DENY:
            return;
        case Filter.ACCEPT:
            break FILTER_LOOP;
        case Filter.NEUTRAL:
            f = f.getNext();
        }
    }
 
    this.append(event);
}
 
protected void subAppend(LoggingEvent event) {
    this.qw.write(this.layout.format(event));
 
    if (layout.ignoresThrowable()) {
        String[] s = event.getThrowableStrRep();
        if (s != null) {
            int len = s.length;
            for (int i = 0; i < len; i++) {
                this.qw.write(s[i]);
                this.qw.write(Layout.LINE_SEP);
            }
        }
    }
 
    if (shouldFlush(event)) {
        this.qw.flush();
    }
}

說明:

1)decide(LoggingEvent event):有三種返回值 DENY、ACCEPT、NEUTRAL,DENY表示丟棄當前日志信息,ACCEPT表示輸出當前日志信息,NEUTRAL表示繼續下一個Filter。Filter只能在XML配置文件中使用,Properties文件中不支持。
2)format(LoggingEvent event):對日志進行格式化處理。
3)write(String string):將日志信息輸出至目的地(文件、數據庫或網格)。

7.Log4j性能優化

1)log4j已成為大型系統必不可少的一部分,log4j可以很方便的幫助我們在程序的任何位置輸出所要打印的信息,便於我們對系統在調試階段和正式運行階段對問題分析和定位。由於日志級別的不同,對系統的性能影響也是有很大的差距,日志級別越高,性能越高。

2)log4j對系統性能的影響程度主要體現在以下幾方面:

3)日志輸出的目的地,輸出到控制台的速度比輸出到文件系統的速度要慢。

4)日志輸出格式不一樣對性能也會有影響,如簡單輸出布局(SimpleLayout)比格式化輸出布局(PatternLayout)輸出速度要快。可以根據需要盡量采用簡單輸出布局格式輸出日志信息。

5)日志級別越低輸出的日志內容就越多,對系統系能影響很大。

6)日志輸出方式的不同,對系統系能也是有一定影響的,采用異步輸出方式比同步輸出方式性能要高。

7)每次接收到日志輸出事件就打印一條日志內容比當日志內容達到一定大小時打印系能要低。

1.針對以上幾點對系能的影響中的第4,5點,對日志配置文件做如下配置:

設置日志緩存,以及緩存大小
log4j.appender.A3.BufferedIO=true
#Buffer單位為字節,默認是8K,IO BLOCK大小默認也是8K
log4j.appender.A3.BufferSize=8192

以上配置說明,當日志內容達到8k時,才會將日志輸出到日志輸出目的地。

2.設置日志輸出為異步方式

 <appender name="DRFOUT" class="org.apache.log4j.DailyRollingFileAppender">
        <param name="File" value="logs/brws.log" />
        <param name="Append" value="true" />
        <param name="DatePattern" value="yyyy_MM_dd'.'" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d [%t] %-5p %l %x - %m%n" />
        </layout>
    </appender>
    <appender name="ASYNCOUT" class="org.apache.log4j.AsyncAppender">
        <param name="BufferSize" value="512" />
        <appender-ref ref="DRFOUT" />
    </appender>

同步情況:各線程直接獲得輸出流進行輸出(線程間不需要同步)。

異步情況:

1)各線程將日志寫到緩存,繼續執行下面的任務(這里是異步的)。
2)日志線程發現需要記日志時獨占緩存(與此同時各線程等待,此時各線程是被阻塞住的),從緩存中取出日志信息,獲得輸出流進行輸出,將緩存解鎖(各線程收到提醒,可以接着寫日志了)。

眾所周知,磁盤IO操作、網絡IO操作、JDBC操作等都是非常耗時的,日志輸出的主要性能瓶頸也就是在寫文件、寫網絡、寫JDBC的時候。日志是肯定要記的,而要采用異步方式記,也就只有將這些耗時操作從主線程當中分離出去才真正的實現性能提升,也只有在線程間同步開銷小於耗時操作時使用異步方式才真正有效!

現在我們接着分別來看看這幾種記錄日志的方式:

1)將日志記錄到本地文件 同樣都是寫本地文件Log4j本身有一個buffer處理入庫,采用異步方式並不一定能提高性能(主要是如何配置好緩存大小);而線程間的同步開銷則是非常大的!因此在使用本地文件記錄日志時不建議使用異步方式。

2)將日志記錄到JMS JMS本身是支持異步消息的,如果不考慮JMS消息創建的開銷,也不建議使用異步方式。

3)將日子記錄到SOCKET 將日志通過Socket發送,純網絡IO操作不需要反饋,因此也不會耗時。

4)將日志記錄到數據庫 眾所周知JDBC是幾種方式中最耗時的:網絡、磁盤、數據庫事務,都使JDBC操作異常的耗時,在這里采用異步方式入庫倒是一個不錯的選擇。

5)將日志記錄到SMTP 同JDBC。

異步輸出日志工作原理:AsyncAppender采用的是生產者消費者的模型進行異步地將Logging Event送到對應的Appender中。

生產者:外部應用了Log4j的系統的實時線程,實時將Logging Event傳送進AsyncAppender里。

中轉:Buffer和DiscardSummary。

消費者:Dispatcher線程和appenders。

工作原理:

1) Logging Event進入AsyncAppender,AsyncAppender會調用append方法,在append方法中會去把logging Event填入Buffer中,當消費能力不如生產能力時,AsyncAppender會把超出Buffer容量的Logging Event放到DiscardSummary中,作為消費速度一旦跟不上生成速度,中轉buffer的溢出處理的一種方案。

2) AsyncAppender有個線程類Dispatcher,它是一個簡單的線程類,實現了Runnable接口。它是AsyncAppender的后台線程。

Dispatcher所要做的工作是:

① 鎖定Buffer,讓其他要對Buffer進行操作的線程阻塞。

② 看Buffer的容量是否滿了,如果滿了就將Buffer中的Logging Event全部取出,並清空Buffer和DiscardSummary;如果沒滿則等待Buffer填滿Logging Event,然后notify Disaptcher線程。

③ 將取出的所有Logging Event交給對應appender進行后面的日志信息推送。

以上是AsyncAppender類的兩個關鍵點:append方法和Dispatcher類,通過這兩個關鍵點實現了異步推送日志信息的功能,這樣如果大量的Logging Event進入AsyncAppender,就可以游刃有余地處理這些日志信息了。

日志框架4:log4j配置_Cape_sir-CSDN博客

1.日志級別

  • 一般日志級別包括:ALL,DEBUG, INFO, WARN, ERROR,FATAL,OFF
  • Log4J推薦使用:DEBUG, INFO,WARN, ERROR

OFF: 為最高等級 關閉了日志信息
FATAL: 為可能導致應用中止的嚴重事件錯誤
ERROR:為嚴重錯誤 主要是程序的錯誤
WARN: 為一般警告,比如session丟失
INFO: 為一般要顯示的信息,比如登錄登出 DEBUG 為程序的調試信息
TRACE: 為比DEBUG更細粒度的事件信息 ALL 為最低等級,將打開所有級別的日志

2.Appender

Appender:日志輸出器,配置日志的輸出級別、輸出位置等,包括以下幾類:

  • ConsoleAppender: 日志輸出到控制台;
  • FileAppender:輸出到文件;
  • RollingFileAppender:輸出到文件,文件達到一定閾值時,自動備份日志文件;
  • DailyRollingFileAppender:可定期備份日志文件,默認一天一個文件,也可設置為每分鍾一個、每小時一個;
  • WriterAppender:可自定義日志輸出位置。

配置日志信息輸出目的地

1.org.apache.log4j.ConsoleAppender(控制台)
2.org.apache.log4j.FileAppender(文件)
3.org.apache.log4j.DailyRollingFileAppender(每天產生一個日志文件)
4.org.apache.log4j.RollingFileAppender(文件大小到達指定尺寸的時候產生一個新的文件)
5.org.apache.log4j.WriterAppender(將日志信息以流格式發送到任意指定的地方)

3.輸出格式

1.org.apache.log4j.HTMLLayout(以HTML表格形式布局),
2.org.apache.log4j.PatternLayout(可以靈活地指定布局模式),
3.org.apache.log4j.SimpleLayout(包含日志信息的級別和信息字符串),
4.org.apache.log4j.TTCCLayout(包含日志產生的時間、線程、類別等等信息)

Log4J最常用的日志輸出格式為:org.apache.log4j.PatternLayOut,其主要參數為:

%n - 換行
%m - 日志內容
%p - 日志級別(FATAL, ERROR,WARN, INFO,DEBUG or custom)
%r - 程序啟動到現在的毫秒數
%t - 當前線程名
%d - 日期和時間, 一般使用格式 %d{yyyy-MM-dd HH:mm:ss,SSS}
%l - 輸出日志事件的發生位置, 同 %F%L%C%M
%F - java 源文件名
%L - java 源碼行數
%C - java 類名,%C{1} 輸出最后一個元素
%M - java 方法名

詳細配置參數如下表:

參數 說明 例子
%c 列出logger名字空間的全稱,如果加上{<層數>}表示列出從最內層算起的指定層數的名字空間 log4j配置文件參數舉例 輸出顯示媒介
假設當前logger名字空間是"a.b.c"
%c a.b.c
%c{2} b.c
%20c (若名字空間長度小於20,則左邊用空格填充)
%-20c (若名字空間長度小於20,則右邊用空格填充)
%.30c (若名字空間長度超過30,截去多余字符)
%20.30c (若名字空間長度小於20,則左邊用空格填充;若名字空間長度超過30,截去多余字符)
%-20.30c (若名字空間長度小於20,則右邊用空格填充;若名字空間長度超過30,截去多余字符)
%C 列出調用logger的類的全名(包含包路徑) 假設當前類是"org.apache.xyz.SomeClass"
%C org.apache.xyz.SomeClass
%C{1} SomeClass
%d 顯示日志記錄時間,{<日期格式>}使用ISO8601定義的日期格式 %d{yyyy/MM/dd HH:mm:ss,SSS} 2005/10/12 22:23:30,117
%d{ABSOLUTE} 22:23:30,117
%d{DATE} 12 Oct 2005 22:23:30,117
%d{ISO8601} 2005-10-12 22:23:30,117
%F 顯示調用logger的源文件名 %F MyClass.java
%l 輸出日志事件的發生位置,包括類目名、發生的線程,以及在代碼中的行數 %l MyClass.main(MyClass.java:129)
%L 顯示調用logger的代碼行 %L 129
%m 顯示輸出消息 %m This is a message for debug.
%M 顯示調用logger的方法名 %M main
%n 當前平台下的換行符 %n Windows平台下表示rn
UNIX平台下表示n
%p 顯示該條日志的優先級 %p INFO
%r 顯示從程序啟動時到記錄該條日志時已經經過的毫秒數 %r 1215
%t 輸出產生該日志事件的線程名 %t MyClass
%x 按NDC(Nested Diagnostic Context,線程堆棧)順序輸出日志 假設某程序調用順序是MyApp調用com.foo.Bar
%c %x - %m%n MyApp - Call com.foo.Bar.
com.foo.Bar - Log in Bar
MyApp - Return to MyApp.
%X 按MDC(Mapped Diagnostic Context,線程映射表)輸出日志。通常用於多個客戶端連接同一台服務器,方便服務器區分是那個客戶端訪問留下來的日志。 %X{5} (記錄代號為5的客戶端的日志)
%% 顯示一個百分號 %% %

4.舉例說明

4.1 log4j.xml舉例

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration PUBLIC "-//log4j/log4j Configuration//EN" "log4j.dtd">
 
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
 
    <!-- 日志輸出到控制台 -->
    <appender name="console" class="org.apache.log4j.ConsoleAppender">
        <!-- 日志輸出格式 -->
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="[%p][%d{yyyy-MM-dd HH:mm:ss SSS}][%c]-[%m]%n"/>
        </layout>
 
        <!--過濾器設置輸出的級別-->
        <filter class="org.apache.log4j.varia.LevelRangeFilter">
            <!-- 設置日志輸出的最小級別 -->
            <param name="levelMin" value="INFO"/>
            <!-- 設置日志輸出的最大級別 -->
            <param name="levelMax" value="ERROR"/>
        </filter>
    </appender>
 
 
    <!-- 輸出日志到文件 -->
    <appender name="fileAppender" class="org.apache.log4j.FileAppender">
        <!-- 輸出文件全路徑名-->
        <param name="File" value="/data/applogs/own/fileAppender.log"/>
        <!--是否在已存在的文件追加寫:默認時true,若為false則每次啟動都會刪除並重新新建文件-->
        <param name="Append" value="false"/>
        <param name="Threshold" value="INFO"/>
        <!--是否啟用緩存,默認false-->
        <param name="BufferedIO" value="false"/>
        <!--緩存大小,依賴上一個參數(bufferedIO), 默認緩存大小8K  -->
        <param name="BufferSize" value="512"/>
        <!-- 日志輸出格式 -->
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="[%p][%d{yyyy-MM-dd HH:mm:ss SSS}][%c]-[%m]%n"/>
        </layout>
    </appender>
 
 
    <!-- 輸出日志到文件,當文件大小達到一定閾值時,自動備份 -->
    <!-- FileAppender子類 -->
    <appender name="rollingAppender" class="org.apache.log4j.RollingFileAppender">
        <!-- 日志文件全路徑名 -->
        <param name="File" value="/data/applogs/RollingFileAppender.log" />
        <!--是否在已存在的文件追加寫:默認時true,若為false則每次啟動都會刪除並重新新建文件-->
        <param name="Append" value="true" />
        <!-- 保存備份日志的最大個數,默認值是:1  -->
        <param name="MaxBackupIndex" value="10" />
        <!-- 設置當日志文件達到此閾值的時候自動回滾,單位可以是KB,MB,GB,默認單位是KB,默認值是:10MB -->
        <param name="MaxFileSize" value="10KB" />
        <!-- 設置日志輸出的樣式 -->`
        <layout class="org.apache.log4j.PatternLayout">
            <!-- 日志輸出格式 -->
            <param name="ConversionPattern" value="[%d{yyyy-MM-dd HH:mm:ss:SSS}] [%-5p] [method:%l]%n%m%n%n" />
        </layout>
    </appender>
 
 
    <!-- 日志輸出到文件,可以配置多久產生一個新的日志信息文件 -->
    <appender name="dailyRollingAppender" class="org.apache.log4j.DailyRollingFileAppender">
        <!-- 文件文件全路徑名 -->
        <param name="File" value="/data/applogs/own/dailyRollingAppender.log"/>
        <param name="Append" value="true" />
        <!-- 設置日志備份頻率,默認:為每天一個日志文件 -->
        <param name="DatePattern" value="'.'yyyy-MM-dd'.log'" />
 
        <!--每分鍾一個備份-->
        <!--<param name="DatePattern" value="'.'yyyy-MM-dd-HH-mm'.log'" />-->
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="[%p][%d{HH:mm:ss SSS}][%c]-[%m]%n"/>
        </layout>
    </appender>
 
 
 
    <!--
        1. 指定logger的設置,additivity是否遵循缺省的繼承機制
        2. 當additivity="false"時,root中的配置就失靈了,不遵循缺省的繼承機制
        3. 代碼中使用Logger.getLogger("logTest")獲得此輸出器,且不會使用根輸出器
    -->
    <logger name="logTest" additivity="false">
        <level value ="INFO"/>
        <appender-ref ref="dailyRollingAppender"/>
    </logger>
 
 
    <!-- 根logger的設置,若代碼中未找到指定的logger,則會根據繼承機制,使用根logger-->
    <root>
        <appender-ref ref="console"/>
        <appender-ref ref="fileAppender"/>
        <appender-ref ref="rollingAppender"/>
        <appender-ref ref="dailyRollingAppender"/>
    </root>
 
</log4j:configuration>

4.2 log4j.properties舉例

#############
# 輸出到控制台
#############

# log4j.rootLogger日志輸出類別和級別:只輸出不低於該級別的日志信息DEBUG < INFO < WARN < ERROR < FATAL
# WARN:日志級別     CONSOLE:輸出位置自己定義的一個名字       logfile:輸出位置自己定義的一個名字
log4j.rootLogger=WARN,CONSOLE,logfile
# 配置CONSOLE輸出到控制台
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender 
# 配置CONSOLE設置為自定義布局模式
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout 
# 配置CONSOLE日志的輸出格式  [frame] 2019-08-22 22:52:12,000  %r耗費毫秒數 %p日志的優先級 %t線程名 %C所屬類名通常為全類名 %L代碼中的行號 %x線程相關聯的NDC %m日志 %n換行
log4j.appender.CONSOLE.layout.ConversionPattern=[frame] %d{yyyy-MM-dd HH:mm:ss,SSS} - %-4r %-5p [%t] %C:%L %x - %m%n

################
# 輸出到日志文件中
################

# 配置logfile輸出到文件中 文件大小到達指定尺寸的時候產生新的日志文件
log4j.appender.logfile=org.apache.log4j.RollingFileAppender
# 保存編碼格式
log4j.appender.logfile.Encoding=UTF-8
# 輸出文件位置此為項目根目錄下的logs文件夾中
log4j.appender.logfile.File=logs/root.log
# 后綴可以是KB,MB,GB達到該大小后創建新的日志文件
log4j.appender.logfile.MaxFileSize=10MB
# 設置滾定文件的最大值3 指可以產生root.log.1、root.log.2、root.log.3和root.log四個日志文件
log4j.appender.logfile.MaxBackupIndex=3  
# 配置logfile為自定義布局模式
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %F %p %m%n

##########################
# 對不同的類輸出不同的日志文件
##########################

# club.bagedate包下的日志單獨輸出
log4j.logger.club.bagedate=DEBUG,bagedate
# 設置為false該日志信息就不會加入到rootLogger中了
log4j.additivity.club.bagedate=false
# 下面就和上面配置一樣了
log4j.appender.bagedate=org.apache.log4j.RollingFileAppender
log4j.appender.bagedate.Encoding=UTF-8
log4j.appender.bagedate.File=logs/bagedate.log
log4j.appender.bagedate.MaxFileSize=10MB
log4j.appender.bagedate.MaxBackupIndex=3
log4j.appender.bagedate.layout=org.apache.log4j.PatternLayout
log4j.appender.bagedate.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %F %p %m%n

日志框架5:logback_Cape_sir-CSDN博客

1.Logback介紹

Logback是由log4j創始人設計的又一個開源日志組件。logback當前分成三個模塊:logback-core,logback-classic和logback-access。logback-core是其它兩個模塊的基礎模塊。logback-classic是log4j的一個改良版本。此外logback-classic完整實現SLF4J API使你可以很方便地更換成其它日志系統如log4j或JDK14 Logging。logback-access訪問模塊與Servlet容器集成提供通過Http來訪問日志的功能。

本文章用到的組件如下:請自行到Maven倉庫下載!

logback-access-1.0.0.jar
logback-classic-1.0.0.jar
logback-core-1.0.0.jar
slf4j-api-1.6.0.jar

maven配置: 這樣依賴包全部自動下載了!

<dependency>  
    <groupId>ch.qos.logback</groupId>  
    <artifactId>logback-classic</artifactId>  
    <version>1.0.11</version>  
</dependency>

2.Logback配置

2.1 Logger、Appender及Layout

Logback建立於三個主要類之上:Logger、Appender 和 Layout。Logger類是logback-classic模塊的一部分,而Appender和Layout接口來自logback-core。作為一個多用途模塊,logback-core不包含任何logger。

Logger作為日志的記錄器,把它關聯到應用的對應的context上后,主要用於存放日志對象,也可以定義日志類型、級別。

Appender主要用於指定日志輸出的目的地,目的地可以是控制台、文件、遠程套接字服務器、 MySQL、 PostreSQL、 Oracle和其他數據庫、 JMS和遠程UNIX Syslog守護進程等。

Layout負責把事件轉換成字符串,格式化的日志信息的輸出。

2.2 LoggerContext

各個logger 都被關聯到一個 LoggerContext,LoggerContext負責制造logger,也負責以樹結構排列各 logger。如果 logger的名稱帶上一個點號后是另外一個 logger的名稱的前綴,那么,前者就被稱為后者的祖先。如果logger與其后代 logger之間沒有其他祖先,那么,前者就被稱為子logger 之父。比如,名為"com.foo""的 logger 是名為"com.foo.Bar"之父。root logger 位於 logger 等級的最頂端,root logger 可以通過其名稱取得,如下所示:

Logger rootLogger =LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);

其他所有logger也通過org.slf4j.LoggerFactory 類的靜態方法getLogger取得。 getLogger方法以 logger 名稱為參數。用同一名字調用LoggerFactory.getLogger 方法所得到的永遠都是同一個logger對象的引用。

2.3 有效級別及級別的繼承

Logger 可以被分配級別。級別包括:TRACE、DEBUG、INFO、WARN 和 ERROR,定義於 ch.qos.logback.classic.Level類。如果 logger沒有被分配級別,那么它將從有被分配級別的最近的祖先那里繼承級別。root logger 默認級別是 DEBUG。

2.4 打印方法與基本的選擇規則

打印方法決定記錄請求的級別。例如,如果 L 是一個 logger 實例,那么,語句 L.info("…")是一條級別為 INFO 的記錄語句。記錄請求的級別在高於或等於其 logger 的有效級別時被稱為被啟用,否則,稱為被禁用。記錄請求級別為 p,其 logger的有效級別為 q,只有則當 p>=q時,該請求才會被執行。

該規則是 logback 的核心。級別排序為: TRACE < DEBUG < INFO < WARN < ERROR。

2.5 Logback默認配置

如果配置文件 logback-test.xml 和 logback.xml 都不存在,那么 logback 默認地會調用BasicConfigurator ,創建一個最小化配置。最小化配置由一個關聯到根 logger 的ConsoleAppender 組成。輸出用模式為%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 的 PatternLayoutEncoder 進行格式化。root logger 默認級別是 DEBUG。

1)Logback的配置文件
Logback 配置文件的語法非常靈活。正因為靈活,所以無法用 DTD 或 XML schema 進行定義。盡管如此,可以這樣描述配置文件的基本結構:以開頭,后面有零個或多個元素,有零個或多個元素,有最多一個元素。

2)Logback默認配置的步驟

  • 嘗試在 classpath 下查找文件 logback-test.xml;
  • 如果文件不存在,則查找文件 logback.xml;
  • 如果兩個文件都不存在,logback 用 BasicConfigurator 自動對自己進行配置,這會導致記錄輸出到控制台。

3)Logback.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<!--
    Copyright 2010-2011 The myBatis Team
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at
        http://www.apache.org/licenses/LICENSE-2.0
    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
-->
<configuration debug="false">
    <!--定義日志文件的存儲地址 勿在 LogBack 的配置中使用相對路徑-->  
    <property name="LOG_HOME" value="/home" />  
    <!-- 控制台輸出 -->   
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> 
             <!--格式化輸出:%d表示日期,%thread表示線程名,%-5level:級別從左顯示5個字符寬度%msg:日志消息,%n是換行符--> 
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>   
        </encoder> 
    </appender>
    <!-- 按照每天生成日志文件 -->   
    <appender name="FILE"  class="ch.qos.logback.core.rolling.RollingFileAppender">   
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件輸出的文件名-->
            <FileNamePattern>${LOG_HOME}/TestWeb.log.%d{yyyy-MM-dd}.log</FileNamePattern> 
            <!--日志文件保留天數-->
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>   
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> 
            <!--格式化輸出:%d表示日期,%thread表示線程名,%-5level:級別從左顯示5個字符寬度%msg:日志消息,%n是換行符--> 
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>   
        </encoder> 
        <!--日志文件最大的大小-->
       <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
         <MaxFileSize>10MB</MaxFileSize>
       </triggeringPolicy>
    </appender> 
   <!-- show parameters for hibernate sql 專為 Hibernate 定制 --> 
    <logger name="org.hibernate.type.descriptor.sql.BasicBinder"  level="TRACE" />  
    <logger name="org.hibernate.type.descriptor.sql.BasicExtractor"  level="DEBUG" />  
    <logger name="org.hibernate.SQL" level="DEBUG" />  
    <logger name="org.hibernate.engine.QueryParameters" level="DEBUG" />
    <logger name="org.hibernate.engine.query.HQLQueryPlan" level="DEBUG" />  
    
    <!--myibatis log configure--> 
    <logger name="com.apache.ibatis" level="TRACE"/>
    <logger name="java.sql.Connection" level="DEBUG"/>
    <logger name="java.sql.Statement" level="DEBUG"/>
    <logger name="java.sql.PreparedStatement" level="DEBUG"/>
    
    <!-- 日志輸出級別 -->
    <root level="INFO">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE" />
    </root> 
     <!--日志異步到數據庫 -->  
    <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
        <!--日志異步到數據庫 --> 
        <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
           <!--連接池 --> 
           <dataSource class="com.mchange.v2.c3p0.ComboPooledDataSource">
              <driverClass>com.mysql.jdbc.Driver</driverClass>
              <url>jdbc:mysql://127.0.0.1:3306/databaseName</url>
              <user>root</user>
              <password>root</password>
            </dataSource>
        </connectionSource>
  </appender>
</configuration>
<?xml version="1.0" encoding="UTF-8"?>
<!--
    scan:當此屬性設置為true時,配置文件如果發生改變,將會被重新加載,默認值為true。
    scanPeriod:設置監測配置文件是否有修改的時間間隔,如果沒有給出時間單位,默認單位是毫秒。當scan為true時,此屬性生效。默認的時間間隔為1分鍾。
    debug:當此屬性設置為true時,將打印出logback內部日志信息,實時查看logback運行狀態。默認值為false。
-->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <property name="APP_NAME" value="base-mvc-web" />
    <!--定義日志文件的存儲地址 勿在 LogBack 的配置中使用相對路徑-->
    <property name="LOG_HOME" value="/home/taomk" />
    <!--
        每個logger都關聯到logger上下文,默認上下文名稱為“default”。但可以使用<contextName>設置成其他名字,用於區分不同應用程序的記錄。一旦設置,不能修改。
     -->
    <contextName>${APP_Name}</contextName>
    <!--
        key:標識此<timestamp> 的名字;datePattern:設置將當前時間(解析配置文件的時間)轉換為字符串的模式,遵循java.txt.SimpleDateFormat的格式。
    -->
    <timestamp key="BOOT_SECOND" datePattern="yyyyMMdd'T'HHmmss"/>
    <!-- 控制台輸出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!--
            encoder:對日志進行格式化,未配置class屬性時,默認配置為PatternLayoutEncoder
        -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化輸出:%d表示日期,%thread表示線程名,%-5level:級別從左顯示5個字符寬度%msg:日志消息,%n是換行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
        <!-- 只輸出level級別的日志 -->
        <filter class = "ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <!--
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
            -->
        </filter>
    </appender>
    <!-- 按照每天生成日志文件 -->
    <appender name="FILE"  class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件輸出的文件名-->
            <FileNamePattern>${LOG_HOME}/${APP_NAME}.log.%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--日志文件保留天數-->
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <!--
            encoder:對日志進行格式化,未配置class屬性時,默認配置為PatternLayoutEncoder
        -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化輸出:%d表示日期,%thread表示線程名,%-5level:級別從左顯示5個字符寬度%msg:日志消息,%n是換行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
        <!--日志文件最大的大小-->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>10MB</MaxFileSize>
        </triggeringPolicy>
        <!-- 只輸出level級別以上的日志 -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
    </appender>
    <!-- 日志異步到數據庫
    <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
        <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
            <dataSource class="com.mchange.v2.c3p0.ComboPooledDataSource">
                <driverClass>com.mysql.jdbc.Driver</driverClass>
                <url>jdbc:mysql://127.0.0.1:3306/databaseName</url>
                <user>root</user>
                <password>root</password>
            </dataSource>
        </connectionSource>
    </appender>
    -->
    <!--
        name:用來指定受此logger約束的某一個包或者具體的某一個類。
        level:用來設置打印級別,大小寫無關:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,還有一個特俗值INHERITED或者同義詞NULL,代表強制執行上級的級別。如果未設置此屬性,那么當前logger將會繼承上級的級別。
        additivity:是否向上級logger傳遞打印信息。默認是true。
        <logger>可以包含零個或多個<appender-ref>元素,標識這個appender將會添加到這個logger。
    -->
    <logger name="net.paoding" level="DEBUG">
        <appender-ref ref="STDOUT" />
    </logger>
    <logger name="net.paoding.rose" level="DEBUG">
        <appender-ref ref="STDOUT" />
    </logger>
    <logger name="net.paoding.rose.RoseFilter" level="DEBUG">
        <appender-ref ref="STDOUT" />
    </logger>
    <logger name="net.paoding.rose.web.controllers.roseInfo" level="DEBUG">
        <appender-ref ref="STDOUT" />
    </logger>
    <logger name="net.paoding.rose.web.controllers.roseInfo.AccessControlInterceptor" level="DEBUG">
        <appender-ref ref="STDOUT" />
    </logger>
    <logger name="net.paoding.rose.web.controllers.roseInfo.TreeController" level="DEBUG">
        <appender-ref ref="STDOUT" />
    </logger>
    <logger name="net.paoding.rose.web.impl.thread.Rose" level="DEBUG">
        <appender-ref ref="STDOUT" />
    </logger>
    <logger name="net.paoding.rose.web.impl.mapping.MappingNode" level="DEBUG">
        <appender-ref ref="STDOUT" />
    </logger>
    <logger name="net.paoding.rose.controllers.ToolsController" level="DEBUG">
        <appender-ref ref="STDOUT" />
    </logger>
    <logger name="net.paoding.rose.jade" level="DEBUG">
        <appender-ref ref="STDOUT" />
    </logger>
    <logger name="org.springframework" level="DEBUG">
        <appender-ref ref="STDOUT" />
    </logger>
    <logger name="org.apache" level="DEBUG">
        <appender-ref ref="STDOUT" />
    </logger>
    <!--
        <root>:也是<logger>元素,但是它是根logger。只有一個level屬性,應為已經被命名為"root".
    -->
    <root level="DEBUG">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE" />
    </root>
</configuration>

4)LogbackDemo.java

 package logback;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    public class LogbackDemo {
        private static Logger log = LoggerFactory.getLogger(LogbackDemo.class);
        public static void main(String[] args) {
            log.trace("======trace");
            log.debug("======debug");
            log.info("======info");
            log.warn("======warn");
            log.error("======error");	 
            String name = "Aub";
            String message = "3Q";
            String[] fruits = { "apple", "banana" };	
            // logback提供的可以使用變量的打印方式,結果為"Hello,Aub!"
            log.info("Hello,{}!", name);	
            // 可以有多個參數,結果為“Hello,Aub! 3Q!”
            log.info("Hello,{}!   {}!", name, message);    		
            // 可以傳入一個數組,結果為"Fruit:  apple,banana"
            log.info("Fruit:  {},{}", fruits); 
        }
    }

3.Spring中LogbackConfigListener使用

在Web.xml中,添加如下配置:

 <!-- logback 日志配置 start -->
    <context-param>
        <param-name>logbackConfigLocation</param-name>
        <param-value>classpath:logback.xml</param-value>
    </context-param>

    <listener>
        <listener-class>ch.qos.logback.ext.spring.web.LogbackConfigListener</listener-class>
    </listener>
    <!-- logback 日志配置 end -->


日志框架6:logback相較於log4j的優勢_Cape_sir-CSDN博客_logback和log4j哪個好

無論從設計上還是實現上,Logback相對log4j而言有了相對多的改進。不過盡管難以一一細數,這里還是列舉部分理由為什么選擇logback而不是log4j。牢記logback與log4j在概念上面是很相似的,它們都是有同一群開發者建立。所以如果你已經對log4j很熟悉,你也可以很快上手logback。如果你喜歡使用log4j,你也許會迷上使用logback。

 

1.更快的執行速度

 

基於我們先前在log4j上的工作,logback 重寫了內部的實現,在某些特定的場景上面,甚至可以比之前的速度快上10倍。在保證logback的組件更加快速的同時,同時所需的內存更加少。

 

2.充分的測試

 

Logback 歷經了幾年,數不清小時數的測試。盡管log4j也是測試過的,但是Logback的測試更加充分,跟log4j不在同一個級別。我們認為,這正是人們選擇Logback而不是log4j的最重要的原因。人們都希望即使在惡劣的條件下,你的日記框架依然穩定而可靠。

 

3.logback-classic 非常自然的實現了SLF4J

 

logback-classic中的登陸類自然的實現了SLF4J。當你使用 logback-classic作為底層實現時,涉及到LF4J日記系統的問題你完全不需要考慮。更進一步來說,由於 logback-classic強烈建議使用SLF4J作為客戶端日記系統實現,如果需要切換到log4j或者其他,你只需要替換一個jar包即可,不需要去改變那些通過SLF4J API 實現的代碼。這可以大大減少更換日記系統的工作量。

 

4.使用XML配置文件或者Groovy

 

配置logback的傳統方法是通過XML文件。在文檔中,大部分例子都是是用XML語法。但是,對於logback版本0.9.22,通過Groovy編寫的配置文件也得到支持。相比於XML,Groovy風格的配置文件更加直觀,連貫和簡短的語法。現在,已經有一個工具自動把logback.xml文件遷移至logback.groovy。

 

5.自動重新載入配置文件

 

Logback-classic可以在配置文件被修改后,自動重新載入。這個掃描過程很快,無資源爭用,並且可以動態擴展支持在上百個線程之間每秒上百萬個調用。它和應用服務器結合良好,並且在J2EE環境通用,因為它不會調用創建一個單獨的線程來做掃描。

 

6.優雅地從I/O錯誤中恢復

 

FileAppender和它的子類,包括RollingFileAppender,可以優雅的從I/O錯誤中恢復。所以,如果一個文件服務器臨時宕機,你再也不需要重啟你的應用,而日志功能就能正常工作。當文件服務器恢復工作,logback相關的appender就會透明地和快速的從上一個錯誤中恢復。

 

7.自動清除舊的日志歸檔文件

 

通過設置TimeBasedRollingPolicy 或者 SizeAndTimeBasedFNATP的 maxHistory 屬性,你就可以控制日志歸檔文件的最大數量。如果你的回滾策略是每月回滾的,並且你希望保存一年的日志,那么只需簡單的設置maxHistory屬性為12。對於12個月之前的歸檔日志文件將被自動清除。

 

8.自動壓縮歸檔日志文件

 

RollingFileAppender可以在回滾操作中,自動壓縮歸檔日志文件。壓縮通常是異步執行的,所以即使是很大的日志文件,你的應用都不會因此而被阻塞。

 

9.謹慎模式

 

在謹慎模式中,在多個JVM中運行的多個FileAppender實例,可以安全的寫入統一個日志文件。謹慎模式可以在一定的限制條件下應用於RollingFileAppender。

 

10.Lilith

 

Lilith是logback的一個記錄和訪問事件查看器。它相當於log4j的 chainsaw,但是Lilith設計的目的是處理大量的日志記錄。

 

11.配置文件中的條件處理

 

開發者通常需要在不同的目標環境中變換logback的配置文件,例如開發環境,測試環境和生產環境。這些配置文件大體是一樣的,除了某部分會有不同。為了避免重復,logback支持配置文件中的條件處理,只需使用,和,那么同一個配置文件就可以在不同的環境中使用了。

 

12.過濾

 

Logback擁有遠比log4j更豐富的過濾能力。例如,讓我們假設,有一個相當重要的商業應用部署在生產環境。考慮到大量的交易數據需要處理,記錄級別被設置為WARN,那么只有警告和錯誤信息才會被記錄。現在,想象一下,你在開發環境遇到了一個臭蟲,但是在測試平台中卻很難發現,因為一些環境之間(生產環境/測試環境)的未知差異。

 

使用log4j,你只能選擇在生產系統中降低記錄的級別到DEBUG,來嘗試發現問題。但是很不幸,這會生成大量的日志記錄,讓分析變得困難。更重要的是,多余的日志記錄會影響到生產環境的性能。

 

使用logback,你可以選擇保留只所有用戶的WARN級別的日志,而除了某個用戶,例如Alice,而她就是問題的相關用戶。當Alice登錄系統,她就會以DEBUG級別被記錄,而其他用戶仍然是以WARN級別來記錄日志。這個功能,可以通過在配置文件的XML中添加4行。

 

13.SiftingAppender

 

SiftingAppender是一個全能的追加器。它可以基於任何給定的實時屬性分開(或者篩選)日志。例如,SiftingAppender可以基於用戶會話分開日志事件,這樣,可以為每一個用戶建立一個獨立的日志文件。

 

14.堆棧軌跡信息包含包的數據

 

當logback打印一個異常,堆棧軌跡信息將包含包的相關數據。下面是一個通過 logback-demo 生成的堆棧信息:

 
14:28:48.835 [btpool0-7] INFO  c.q.l.demo.prime.PrimeAction - 99 is not a valid value
java.lang.Exception: 99 is invalid
  at ch.qos.logback.demo.prime.PrimeAction.execute(PrimeAction.java:28) [classes/:na]
  at org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:431) [struts-1.2.9.jar:1.2.9]
  at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:236) [struts-1.2.9.jar:1.2.9]
  at org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:432) [struts-1.2.9.jar:1.2.9]
  at javax.servlet.http.HttpServlet.service(HttpServlet.java:820) [servlet-api-2.5-6.1.12.jar:6.1.12]
  at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:502) [jetty-6.1.12.jar:6.1.12]
  at ch.qos.logback.demo.UserServletFilter.doFilter(UserServletFilter.java:44) [classes/:na]
  at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115) [jetty-6.1.12.jar:6.1.12]
  at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:361) [jetty-6.1.12.jar:6.1.12]
  at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:417) [jetty-6.1.12.jar:6.1.12]
  at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:230) [jetty-6.1.12.jar:6.1.12]
 

從上面的信息,你可以發現這個應用使用Struts 1.2.9 而且是使用 jetty 6.1.12部署的。所以,堆棧軌跡信息將快速的告訴讀者,關於異常發生的類還有包和包的版本。當你的客戶發送一個堆棧軌跡信息給你,作為一個開發人員,你就不需要讓他們告訴你他們正在使用的包的版本。這項信息已經包括在堆棧軌跡信息中。詳細請參考 “%xThrowable” conversion word.

 

15.Logback-access模塊,提供了通過HTTP訪問日志的能力,是logback不可或缺的組成部分

 

最后但絕非最不重要的是,作為logback發布包的一部分,logback-access模塊可與Jetty或者Tomcat進行集成,提供了非常豐富而強大的通過HTTP訪問日志的功能。因為logback-access模塊是logback初期設計方案中的一部分,因此,所有你所喜歡的logback-classic模塊所提供的全部特性logback-access同樣也具備。

日志框架7:log4j2_Cape_sir-CSDN博客_log4j2日志框架

1.Log4j2介紹

Log4j2是Log4j的升級版,與之前的版本Log4j 1.x相比、有重大的改進,在修正了Logback固有的架構問題的同時,改進了許多Logback所具有的功能。

1.1 Log4j2的特性及改進

  • API分離:Log4j2將API與實現分離開來。開發人員現在可以很清楚地知道能夠使用哪些沒有兼容問題的類和方法,同時又允許通過自己實現來增強功能。
  • 改進的性能:Log4j2的性能在某些關鍵領域比Log4j 1.x更快,而且大多數情況下與Logback相當。
    多個API支持:Log4j2提供最棒的性能的同時,還支持SLF4J和公共日志記錄API。
  • 自動配置加載:像Logback一樣,一旦配置發生改變,Log4j2可以自動載入這些更改后的配置信息,又與Logback不同,配置發生改變時不會丟失任何日志事件。
  • 高級過濾功能:與Logback類似,Log4j2可以支持基於上下文數據、標記,正則表達式以及日志事件中的其他組件的過濾。Log4j2能夠專門指定適用於所有的事件,無論這些事件在傳入Loggers之前還是正在傳給appenders。另外,過濾器還可以與Loggers關聯起來。與Logback不同的是,Filter公共類可以用於任何情況。
  • 插件架構:所有可以配置的組件都以Log4j插件的形式來定義。同樣地,不需要修改任何Log4j代碼就可以創建新的Appender、Layout、Pattern Convert 等等。Log4j自動識別預定義的插件,如果在配置中引用到這些插件,Log4j就自動載入使用。
  • 屬性支持:屬性可以在配置文件中引用,也可以直接替代或傳入潛在的組件,屬性在這些組件中能夠動態解析。屬性可以是配置文件,系統屬性,環境變量,線程上下文映射以及事件中的數據中定義的值。用戶可以通過增加自己的Lookup插件來定制自己的屬性。

1)更為先進的API(Modern API) 在這之前,程序員們以如下方式進行日志記錄:

if(logger.isDebugEnabled()) {
    logger.debug("Hi, " + u.getA() + “ “ + u.getB());
}

許多人都會抱怨上述代碼的可讀性太差了。如果有人忘記寫if語句,程序輸出中會多出很多不必要的字符串。現在,Java虛擬機(JVM)也許對字符串的打印和輸出進行了很多優化,但是難道我們僅僅依靠JVM優化來解決上述問題?log4j 2.0開發團隊鑒於以上考慮對API進行了完善。現在你可以這樣寫代碼:

logger.debug("Hi, {} {}", u.getA(), u.getB());

和其它一些流行的日志框架一樣, 新的API也支持變量參數的占位符功能。

log4j 2.0還支持其它一些很棒的功能,像Markers和flow tracing:

private Logger logger = LogManager.getLogger(MyApp.class.getName());
private static final Marker QUERY_MARKER = MarkerManager.getMarker("SQL");
...
public String doQuery(String table) {
    logger.entry(param);
    logger.debug(QUERY_MARKER, "SELECT * FROM {}", table);
    return logger.exit();
}

Markers可以幫助你很快地找到具體的日志項(Log Entries)。而在某個方法的開頭和結尾調用Flow Traces中的一些方法,你可以在日志文件中看到很多新的跟蹤層次的日志項,也就是說,你的程序工作流(Program Flow)被記錄下來了。下面是Flow Traces的一些例子:

19:08:07.056 TRACE com.test.TestService 19 retrieveMessage - entry
19:08:07.060 TRACE com.test.TestService 46 getKey - entry

插件式的架構:log4j 2.0支持插件式的架構。你可以根據需要自行擴展log4j 2.0,這非常簡單。首先,你要為你的擴展建立好命名空間,然后告訴log4j 2.0在哪能夠找到它。

<configuration … packages="de.grobmeier.examples.log4j2.plugins">

根據上述配置,log4j 2將會在de.grobmeier.examples.log4j2.plugins包中找尋你的擴展插件。如果你建立了多個命名空間,沒關系,用逗號分隔就可以了。

下面是一個簡單的擴展插件:

@Plugin(name = "Sandbox", type = "Core", elementType = "appender")
public class SandboxAppender extends AppenderBase {

    private SandboxAppender(String name, Filter filter) {
        super(name, filter, null);
    }
 
    public void append(LogEvent event) {
        System.out.println(event.getMessage().getFormattedMessage());
    }
 
    @PluginFactory
    public static SandboxAppender createAppender(
         @PluginAttr("name") String name,
         @PluginElement("filters") Filter filter) {
        return new SandboxAppender(name, filter);
    }
}

上面標有@PluginFactory注解的方法是一個工廠,它的兩個參數直接從配置文件讀取。我用@PluginAttr和@PluginElement進行了實現。

剩下的就非常簡單了。由於我寫的是一個Appender,因此得繼承AppenderBase這個類。該類必須實現append()方法,從而進行實際的邏輯處理。除了Appender,你甚至可以實現自己的Logger和Filter。

2)強大的配置功能(Powerful Configuration) log4j 2的配置變得非常簡單。如果你習慣了之前的配置方式,也不用擔心,你只要花很少的時間就可以從之前的方式轉換到新的方式。請看下面的配置:

<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
    <appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
    </appenders>
    <loggers>
        <logger name="com.foo.Bar" level="trace" additivity="false">
            <appender-ref ref="Console"/>
        </logger>
        <root level="error">
            <appender-ref ref="Console"/>
        </root>
    </loggers>
</configuration>

上面說的只是一部分改進,你還可以自動重新加載配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<configuration monitorInterval="30">
    ...
</configuration>

監控的時間間隔單位為秒,最小值是5。這意味着,log4j 2在配置改變的情況下可以重新配置日志記錄行為。如果值設置為0或負數,log4j 2不會對配置變更進行監測。最為稱道的一點是:不像其它日志框架, log4j 2.0在重新配置的時候不會丟失之前的日志記錄。

還有一個非常不錯的改進,那就是:同XML相比,如果你更加喜歡JSON,你可以自由地進行基於JSON的配置了:

{
    "configuration": {
        "appenders": {
            "Console": {
                "name": "STDOUT",
                "PatternLayout": {
                    "pattern": "%m%n"
                }
            }
        },
        "loggers": {
            "logger": {
                "name": "EventLogger",
                "level": "info",
                "additivity": "false",
                "appender-ref": {
                    "ref": "Routing"
                }
            },
            "root": {
                "level": "error",
                "appender-ref": {
                    "ref": "STDOUT"
                }
            }
        }
    }
}

3)Java 5 並發性(Concurrency)
有一段文檔是這樣描述的:“log4j 2利用Java 5中的並發特性支持,盡可能地執行最低層次的加鎖…”。Apache log4j 2.0解決了許多在log4j 1.x中仍然存留的死鎖問題。如果你的程序仍然飽受內存泄漏的折磨,請毫不猶豫地試一下log4j 2.0。

1.2 程序中如何使用

在你的程序中使用Log4j之前必須確保API和Core jars 在程序的classpath中。使用Maven將下面的依賴加入pom.xml。

<dependencies>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>2.0-beta3</version>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.0-beta3</version>
    </dependency>
</dependecies>

與日志門面接口commons-logging,slf4j集成使用,具體詳情參見slf4j與log4j2集成,commons-logging與log4j2集成。

這里隨便寫個類,調用就是這么簡單,log4j的核心在配置文件上,配置文件詳解,請參見Log4j2配置文件詳解。

 public class Log4j2Hello {
        private static Logger logger = LogManager.getLogger(Hello.class.getName());
        public boolean hello() {
            logger.entry();   //trace級別的信息,單獨列出來是希望你在某個方法或者程序邏輯開始的時候調用,和logger.trace("entry")基本一個意思
            logger.error("Did it again!");   //error級別的信息,參數就是你輸出的信息
            logger.info("我是info信息");    //info級別的信息
            logger.debug("我是debug信息");
            logger.warn("我是warn信息");
            logger.fatal("我是fatal信息");
            logger.log(Level.DEBUG, "我是debug信息");   //這個就是制定Level類型的調用:誰閑着沒事調用這個,也不一定哦!
            logger.exit();    //和entry()對應的結束方法,和logger.trace("exit");一個意思
            return false;
        }
    }

2.Log4j2日志級別

在log4j2中,一共有五種log level,分別為TRACE, DEBUG,INFO, WARN, ERROR 以及FATAL。詳細描述如下:

  • FATAL:用在極端的情形中,即必須馬上獲得注意的情況。這個程度的錯誤通常需要觸發運維工程師的尋呼機。
  • ERROR:顯示一個錯誤,或一個通用的錯誤情況,但還不至於會將系統掛起。這種程度的錯誤一般會觸發郵件的發送,將消息發送到alert list中,運維人員可以在文檔中記錄這個bug並提交。
  • WARN:不一定是一個bug,但是有人可能會想要知道這一情況。如果有人在讀log文件,他們通常會希望讀到系統出現的任何警告。
  • INFO:用於基本的、高層次的診斷信息。在長時間運行的代碼段開始運行及結束運行時應該產生消息,以便知道現在系統在干什么。但是這樣的信息不宜太過頻繁。
  • DEBUG:用於協助低層次的調試。
  • TRACE:用於展現程序執行的軌跡。

3.Log4j2類圖

在這里插入圖片描述
通過類圖可以看到:

每一個log上下文對應一個configuration,configuration中詳細描述了log系統的各個LoggerConfig、Appender(輸出目的地)、EventLog過濾器等。每一個Logger又與一個LoggerConfig相關聯。另外,可以看到Filter的種類很多,有聚合在Configuration中的filter、有聚合在LoggerConfig中的filter也有聚合在Appender中的filter。不同的filter在過濾LogEvent時的行為和判斷依據是不同的,具體可參加本文后面給出的文檔。

應用程序通過調用log4j2的API並傳入一個特定的名稱來向LogManager請求一個Logger實例。LogManager會定位到適當的 LoggerContext 然后通過它獲得一個Logger。如果LogManager不得不新建一個Logger,那么這個被新建的Logger將與LoggerConfig相關聯,這個LoggerConfig的名稱中包含如下信息中的一種:①與Logger名稱相同的②父logger的名稱③ root 。

當一個LoggerConfig的名稱與一個Logger的名稱可以完全匹配時,Logger將會選擇這個LoggerConfig作為自己的配置。如果不能完全匹配,那么Logger將按照最長匹配串來選擇自己所對應的LoggerConfig。LoggerConfig對象是根據配置文件來創建的。LoggerConfig會與Appenders相關聯,Appenders用來決定一個log request將被打印到那個目的地中,可選的打印目的地很多,如console、文件、遠程socket server等。LogEvent是由Appenders來實際傳遞到最終輸出目的地的,而在LogEvent到達最終被處理之前,還需要經過若干filter的過濾,用來判斷該EventLog應該在何處被轉發、何處被駁回、何處被執行。

4.Log4j2概念介紹

4.1 Logger層次關系

相比於純粹的System.out.println方式,使用logging API的最首要以及最重要的優勢是可以在禁用一些log語句塊的同時允許其他的語句塊的輸出。這一能力建立在一種假設之上,即所有在應用中可能出現的logging語句可以按照開發者定義的標准分成不同的類型。

在Log4j 1.x版本時,Logger的層次是靠Logger類之間的關系來維護的。但在Log4j2中, Logger的層次則是靠LoggerConfig對象之間的關系來維護的。

Logger和LoggerConfig均是有名稱的實體。Logger的命名是大小寫敏感的,並且服從如下的分層命名規則。(與java包的層級關系類似)。例如:com.foo是com.foo.Bar的父級;java是java.util的父級,是java.util.vector的祖先。

root LoggerConfig位於LoggerConfig層級關系的最頂層。它將永遠存在與任何LoggerConfig層次中。任何一個希望與root LoggerConfig相關聯的Logger可以通過如下方式獲得:

Logger logger = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);

其他的Logger實例可以調用LogManager.getLogger 靜態方法並傳入想要得到的Logger的名稱來獲得。

4.2 LoggerContext

LoggerContext在Logging System中扮演了錨點的角色。根據情況的不同,一個應用可能同時存在於多個有效的LoggerContext中。在同一LoggerContext下,log system是互通的。如:Standalone Application、Web Applications、Java EE Applications、“Shared” Web Applications 和REST Service Containers,就是不同廣度范圍的log上下文環境。

4.3 Configuration

每一個LoggerContext都有一個有效的Configuration。Configuration包含了所有的Appenders、上下文范圍內的過濾器、LoggerConfigs以及StrSubstitutor.的引用。在重配置期間,新與舊的Configuration將同時存在。當所有的Logger對象都被重定向到新的Configuration對象后,舊的Configuration對象將被停用和丟棄。

4.4 Logger

如前面所述, Loggers 是通過調用LogManager.getLogger方法獲得的。Logger對象本身並不實行任何實際的動作。它只是擁有一個name 以及與一個LoggerConfig相關聯。它繼承了AbstractLogger類並實現了所需的方法。當Configuration改變時,Logger將會與另外的LoggerConfig相關聯,從而改變這個Logger的行為。

獲得Logger,使用相同的名稱參數來調用getLogger方法將獲得來自同一個Logger的引用。如:

Logger x = Logger.getLogger("wombat");

Logger y = Logger.getLogger("wombat"); // x和y指向的是同一個Logger對象。

log4j環境的配置是在應用的啟動階段完成的。優先進行的方式是通過讀取配置文件來完成。

log4j使采用類名(包括完整路徑)來定義Logger 名變得很容易。這是一個很有用且很直接的Logger命名方式。使用這種方式命名可以很容易的定位這個log message產生的類的位置。當然,log4j也支持任意string的命名方式以滿足開發者的需要。不過,使用類名來定義Logger名仍然是最為推崇的一種Logger命名方式。

4.5 LoggerConfig

當Logger在configuration中被描述時,LoggerConfig對象將被創建。LoggerConfig包含了一組過濾器。LogEvent在被傳往Appender之前將先經過這些過濾器。過濾器中包含了一組Appender的引用。Appender則是用來處理這些LogEvent的。

每一個LoggerConfig會被指定一個Log級別。可用的Log級別包括TRACE, DEBUG,INFO, WARN, ERROR 以及FATAL。需要注意的是,在log4j2中,Log的級別是一個Enum型變量,是不能繼承或者修改的。如果希望獲得更多的分割粒度,可用考慮使用Markers來替代。

在Log4j 1.x 和Logback 中都有“層次繼承”這么個概念。但是在log4j2中,由於Logger和LoggerConfig是兩種不同的對象,因此“層次繼承”的概念實現起來跟Log4j 1.x 和Logback不同。具體情況下面的五個例子:

例子一:
輸入圖片說明

可用看到,應用中的LoggerConfig只有root這一種。因此,對於所有的Logger而言,都只能與該LoggerConfig相關聯而沒有別的選擇。

例子二:
輸入圖片說明

在例子二中可以看到,有5種不同的LoggerConfig存在於應用中,而每一個Logger都被與最匹配的LoggerConfig相關聯着,並且擁有不同的Log Level。

例子三:
輸入圖片說明

可以看到Logger root、X、X.Y.Z都找到了與各種名稱相同的LoggerConfig。而LoggerX.Y沒有與其名稱相完全相同的LoggerConfig。怎么辦呢?它最后選擇了X作為它的LoggerConfig,因為X LoggerConfig擁有與其最長的匹配度。

例子四:
輸入圖片說明

可以看到,現在應用中有兩個配置好的LoggerConfig:root和X。而Logger有四個:root、X、X.Y、X.Y.Z。其中,root和X都能找到完全匹配的LoggerConfig,而X.Y和X.Y.Z則沒有完全匹配的LoggerConfig,那么它們將選擇哪個LoggerConfig作為自己的LoggerConfig呢?由圖上可知,它們都選擇了X而不是root作為自己的LoggerConfig,因為在名稱上,X擁有最長的匹配度。

例子五:
輸入圖片說明

可以看到,現在應用中有三個配置好的LoggerConfig,分別為:root、X、X.Y。同時,有四個Logger,分別為:root、X、X.Y以及X.YZ。其中,名字能完全匹配的是root、X、X.Y。那么剩下的X.YZ應該匹配X還是匹配X.Y呢?答案是X。因為匹配是按照標記點(即“.”)來進行的,只有兩個標記點之間的字串完全匹配才算,否則將取上一段完全匹配的字串的長度作為最終匹配長度。

4.6 Filter

與防火牆過濾的規則相似,log4j2的過濾器也將返回三類狀態:Accept(接受), Deny(拒絕) 或Neutral(中立)。其中,Accept意味着不用再調用其他過濾器了,這個LogEvent將被執行;Deny意味着馬上忽略這個event,並將此event的控制權交還給過濾器的調用者;Neutral則意味着這個event應該傳遞給別的過濾器,如果再沒有別的過濾器可以傳遞了,那么就由現在這個過濾器來處理。

4.7 Appender

由logger的不同來決定一個logging request是被禁用還是啟用只是log4j2的情景之一。log4j2還允許將logging request中log信息打印到不同的目的地中。在log4j2的世界里,不同的輸出位置被稱為Appender。目前,Appender可以是console、文件、遠程socket服務器、Apache Flume、JMS以及遠程 UNIX 系統日志守護進程。一個Logger可以綁定多個不同的Appender。

可以調用當前Configuration的addLoggerAppender函數來為一個Logger增加。如果不存在一個與Logger名稱相對應的LoggerConfig,那么相應的LoggerConfig將被創建,並且新增加的Appender將被添加到此新建的LoggerConfig中。爾后,所有的Loggers將會被通知更新自己的LoggerConfig引用(PS:一個Logger的LoggerConfig引用是根據名稱的匹配長度來決定的,當新的LoggerConfig被創建后,會引發一輪配對洗牌)。

在某一個Logger中被啟用的logging request將被轉發到該Logger相關聯的的所有Appenders上,並且還會被轉發到LoggerConfig的父級的Appenders上。

這樣會產生一連串的遺傳效應。例如,對LoggerConfig B來說,它的父級為A,A的父級為root。如果在root中定義了一個Appender為console,那么所有啟用了的logging request都會在console中打印出來。另外,如果LoggerConfig A定義了一個文件作為Appender,那么使用LoggerConfig A和LoggerConfig B的logger 的logging request都會在該文件中打印,並且同時在console中打印。

如果想避免這種遺傳效應的話,可以在configuration文件中做如下設置:

additivity="false" // 默認為true

這樣,就可以關閉Appender的遺傳效應了。

4.8 Layout

通常,用戶不止希望能定義log輸出的位置,還希望可以定義輸出的格式。這就可以通過將Appender與一個layout相關聯來實現。Log4j中定義了一種類似C語言printf函數的打印格式,如"%r [%t] %-5p %c - %m%n" 格式在真實環境下會打印類似如下的信息:

176 [main] INFO  org.foo.Bar - Located nearest gas station.

其中,各個字段的含義分別是:

%r 指的是程序運行至輸出這句話所經過的時間(以毫秒為單位);
%t 指的是發起這一log request的線程;
%c 指的是log的level;
%m 指的是log request語句攜帶的message;
%n 為換行符;

5.Log4j2的LogEvent分析

5.1 如何產生LogEvent

在調用Logger對象的info、error、trace等函數時,就會產生LogEvent。LogEvent跟LoggerConfig一樣,也是由Level的。LogEvent的Level主要是用在Event傳遞時,判斷在哪里停下。

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Test {
    private static Logger logger = LogManager.getLogger("HelloWorld");
    public static void main(String[] args){
        Test.logger.info("hello,world");
        Test.logger.error("There is a error here");
    }

}

如代碼中所示,這樣就產生了兩個LogEvent。

5.2 LogEvent的傳遞是怎樣的

我們在IDE中運行一下這個程序,看看會有什么輸出。如下:

輸入圖片說明

發現,只有ERROR的語句輸出了,那么INFO的語句呢?不着急,先來看看工程目錄結構:

在這里插入圖片描述

可以看到,工程中沒有寫入任何的配置文件。所以,application應該是使用了默認的LoggerConfig Level。那么默認的Level是多少呢?默認的輸出地是console,默認的級別是ERROR級別。

那么,為什么默認ERROR級別會導致INFO級別的信息被攔截呢?看如下表格:

輸入圖片說明

左邊豎欄是Event的Level,右邊橫欄是LoggerConfig的Level。Yes的意思就是這個event可以通過filter,no的意思就是不能通過filter。

可以看到,INFO級別的Event是無法被ERROR級別的LoggerConfig的filter接受的。所以,INFO信息不會被輸出。

6.Log4j2的配置文件重定位

如果想要改變默認的配置,那么就需要configuration file。Log4j的配置是寫在log4j.properties文件里面,但是Log4j2就可以寫在XML和JSON文件里了。

(1)放在classpath(src)下,以log4j2.xml命名:使用Log4j2的一般都約定俗成的寫一個log4j2.xml放在src目錄下使用。這一點沒有爭議。

(2)將配置文件放到別處:在系統工程里面,將log4j2的配置文件放到src目錄底下很不方便。如果能把工程中用到的所有配置文件都放在一個文件夾里面,當然就更整齊更好管理了。但是想要實現這一點,前提就是Log4j2的配置文件能重新定位到別處去,而不是放在classpath底下。

如果沒有設置"log4j.configurationFile" system property的話,application將在classpath中按照如下查找順序來找配置文件:

log4j2-test.json 或log4j2-test.jsn文件

log4j2-test.xml文件

log4j2.json 或log4j2.jsn文件

log4j2.xml文件

這就是為什么在src目錄底下放log4j2.xml文件可以被識別的原因了。如果想將配置文件重命名並放到別處,就需要設置系統屬性log4j.configurationFile。設置的方式是在VM arguments中寫入該屬性的key和value:

-Dlog4j.configurationFile="D:\learning\blog\20130115\config\LogConfig.xml"

測試的java程序如上文,在此不再重復。運行,console輸出如下:

輸入圖片說明

期待已久的INFO語句出現了。

 日志框架8:log4j2配置_Cape_sir-CSDN博客

1.默認配置

本來以為Log4J2應該有一個默認的配置文件的,不過好像沒有找到(通過DefaultConfiguration,初始化一個最小化配置),下面這個配置文件等同於缺省配置:

<?xml version="1.0" encoding="UTF-8"?>  
<configuration status="OFF">  
    <appenders>  
        <Console name="Console" target="SYSTEM_OUT">  
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>  
        </Console>  
    </appenders>  
    <loggers>  
        <root level="error">  
            <appender-ref ref="Console"/>  
        </root>  
    </loggers>  
</configuration>

2.第一個配置例子

配置Log4j 2可以有四種方法(其中任何一種都可以):

  • 通過一個格式為XML或JSON的配置文件。
  • 以編程方式,通過創建一個ConfigurationFactory工廠和Configuration實現。
  • 以編程方式,通過調用api暴露在配置界面添加組件的默認配置。
  • 以編程方式,通過調用Logger內部類上的方法。

注意,與Log4j 1.x不一樣的地方,公開的Log4j 2 API沒有提供任何添加、修改或刪除 appender和過濾器或者操作配置文件的方法。

Log4j能夠自動配置本身在初始化期間。當Log4j啟動它將定位所有的ConfigurationFactory插件和安排然后在加權從最高到最低。Log4j包含兩個ConfigurationFactory實現,一個用於JSON和XML。加載配置文件流程如下:

1)Log4j將檢查“Log4j的配置文件“系統屬性,如果設置,將嘗試加載配置使用 ConfigurationFactory 匹配的文件擴展。
2)如果沒有系統屬性設置JSON ConfigurationFactory log4j2-test將尋找。 json或 log4j2-test。json在類路徑中。
3)如果沒有這樣的文件發現XML ConfigurationFactory log4j2-test將尋找。 xml在 類路徑。
4)如果一個測試文件無法找到JSON ConfigurationFactory log4j2將尋找。 log4j2.jsn json或 在類路徑中。
5)如果一個JSON文件無法找到XML ConfigurationFactory將試圖定位 log4j2。 xml在類路徑中。
6)如果沒有配置文件可以找到了 DefaultConfiguration 將被使用。 這將導致日志輸出到控制台。

<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
    <appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
    </appenders>
    <loggers>
        <!--我們只讓這個logger輸出trace信息,其他的都是error級別-->
        <!--
        additivity開啟的話,由於這個logger也是滿足root的,所以會被打印兩遍。
        不過root logger 的level是error,為什么Bar 里面的trace信息也被打印兩遍呢
        -->
        <logger name="cn.lsw.base.log4j2.Hello" level="trace" additivity="false">
            <appender-ref ref="Console"/>
        </logger>
        <root level="error">
            <appender-ref ref="Console"/>
        </root>
    </loggers>
</configuration>

我們這里看到了配置文件里面是name很重要,沒錯,這個name可不能隨便起(其實可以隨便起)。這個機制意思很簡單。就是類似於java package一樣,比如我們的一個包:cn.lsw.base.log4j2。而且,可以發現我們前面生成Logger對象的時候,命名都是通過 Hello.class.getName(); 這樣的方法,為什么要這樣呢? 很簡單,因為有所謂的Logger 繼承的問題。比如 如果你給cn.lsw.base定義了一個logger,那么他也適用於cn.lsw.base.lgo4j2這個logger。名稱的繼承是通過點(.)分隔的。然后你可以猜測上面loggers里面有一個子節點不是logger而是root,而且這個root沒有name屬性。這個root相當於根節點。你所有的logger都適用與這個logger,所以,即使你在很多類里面通過類名.class.getName() 得到很多的logger,而且沒有在配置文件的loggers下面做配置,他們也都能夠輸出,因為他們都繼承了root的log配置。

我們上面的這個配置文件里面還定義了一個logger,他的名稱是 cn.lsw.base.log4j2.Hello ,這個名稱其實就是通過前面的Hello.class.getName(); 得到的,我們為了給他單獨做配置,這里就生成對於這個類的logger,上面的配置基本的意思是只有cn.lsw.base.log4j2.Hello 這個logger輸出trace信息,也就是他的日志級別是trace,其他的logger則繼承root的日志配置,日志級別是error,只能打印出ERROR及以上級別的日志。如果這里logger 的name屬性改成cn.lsw.base,則這個包下面的所有logger都會繼承這個log配置(這里的包是log4j的logger name的“包”的含義,不是java的包,你非要給Hello生成一個名稱為“myhello”的logger,他也就沒法繼承cn.lsw.base這個配置了。

那有人就要問了,他不是也應該繼承了root的配置了么,那么會不會輸出兩遍呢?我們在配置文件中給了解釋,如果你設置了additivity=“false”,就不會輸出兩遍。

3.復雜一點的配置

 <?xml version="1.0" encoding="UTF-8"?>
    <!--
        Configuration后面的status,這個用於設置log4j2自身內部的信息輸出,可以不設置,當設置成trace時,你會看到log4j2內部各種詳細輸出。 
    -->
    <!--
        monitorInterval:Log4j能夠自動檢測修改配置 文件和重新配置本身,設置間隔秒數。
    -->
    <configuration status="error" monitorInterval=”30″>
        <!--先定義所有的appender-->
        <appenders>
            <!--這個輸出控制台的配置-->
            <Console name="Console" target="SYSTEM_OUT">
                <!--控制台只輸出level及以上級別的信息(onMatch),其他的直接拒絕(onMismatch)-->
                <ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY"/>
                <!--這個都知道是輸出日志的格式-->
                <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
            </Console>
            <!--文件會打印出所有信息,這個log每次運行程序會自動清空,由append屬性決定,這個也挺有用的,適合臨時測試用-->
            <File name="log" fileName="log/test.log" append="false">
                <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
            </File> 
            <!-- 這個會打印出所有的信息,每次大小超過size,則這size大小的日志會自動存入按年份-月份建立的文件夾下面並進行壓縮,作為存檔-->
            <RollingFile name="RollingFile" fileName="logs/app.log"
                     filePattern="log/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
                <PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
                <SizeBasedTriggeringPolicy size="50MB"/>
                <!-- DefaultRolloverStrategy屬性如不設置,則默認為最多同一文件夾下7個文件,這里設置了20 -->
                <DefaultRolloverStrategy max="20"/>
            </RollingFile>
        </appenders>
        <!--然后定義logger,只有定義了logger並引入的appender,appender才會生效-->
        <loggers>
            <!--建立一個默認的root的logger-->
            <root level="trace">
                <appender-ref ref="RollingFile"/>
                <appender-ref ref="Console"/>
            </root> 
        </loggers>
    </configuration> 
  • 擴展組件
    1)ConsoleAppender:輸出結果到System.out或是System.err。
    2)FileAppender:輸出結果到指定文件,同時可以指定輸出數據的格式。append=“false”指定不追加到文件末尾
    3)RollingFileAppender:自動追加日志信息到文件中,直至文件達到預定的大小,然后自動重新生成另外一個文件來記錄之后的日志。
  • 過濾標簽
    1)ThresholdFilter:用來過濾指定優先級的事件。
    2)TimeFilter:設置start和end,來指定接收日志信息的時間區間。

3.1 Appender之Syslog配置

log4j2中對syslog的簡單配置,這里就不重復展示log4j2.xml了:

<Syslog name="SYSLOG" host="localhost" port="514" protocol="UDP" facility="LOCAL3"/>

host是指你將要把日志寫到的目標機器,可以是ip(本地ip或遠程ip,遠程ip在實際項目中很常見,有專門的日志服務器來存儲日志),也可以使用主機名,如果是本地,還可以使用localhost或127.0.0.1。

Port指定端口,默認514,參見/etc/rsyslog.conf(以Fedora系統為例,下同)。protocol指定傳輸協議,這里是UDP,facility是可選項,后面可以看到用法。

3.2 Syslog及Syslog-ng相關配置(Fedora)

在運行程序之前,需要修改:/etc/rsyslog.conf。

把這兩行前的#去掉,即取消注釋:

#$ModLoad imudp
#$UDPServerRun 514

這里啟用udp監聽,514是默認監聽端口,重啟syslog:

service syslog restart

大部分日志會默認寫到/var/log/messages中,如果不想寫到這個文件里,可以按下面修改,這樣local3的日志就會寫到app.log中。這里的local3即 log4j2.xml中facility的配置。

*.info;mail.none;authpriv.none;cron.none;local3.none                /var/log/messages

新增一行:

local3.*                                                            /var/log/app.log

除了使用自帶的syslog,我們也可以使用syslog的替代品,比如syslog-ng,這對於log4j2.xml配置沒有影響。 安裝:

yum install syslog-ng

啟動:

service syslog-ng start

其配置文件為:

/etc/syslog-ng/syslog-ng.conf

啟動前把source一節中這一行取消注釋即可:

#udp(ip(0.0.0.0) port(514));

這個端口會和syslog沖突,可以使用別的端口比如50014,同時修改log4j2.xml中的port屬性。另外提一下,使用非默認端口,要求log4j版本在1.2.15或以上。

syslog-ng本身也可以設置把日志送到遠程機器上,在源機器上的syslog-ng.conf中添加:

destination d_remote1 {udp(153.65.171.73 port(514));};

這表示源機器上的syslog-ng會把接收到的日志送到遠程主機153.65.171.73的514端口上,只要保證153.65.171.73上的syslog-ng正常運行並監聽對應的端口即可。

4.Log4j2與Spring集成

web.xml配置:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/root-context.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- log4j2-begin -->
    <listener>
        <listener-class>org.apache.logging.log4j.web.Log4jServletContextListener</listener-class>
    </listener>
    <filter>
        <filter-name>log4jServletFilter</filter-name>
        <filter-class>org.apache.logging.log4j.web.Log4jServletFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>log4jServletFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>INCLUDE</dispatcher>
        <dispatcher>ERROR</dispatcher>
    </filter-mapping>
    <!-- log4j2-end -->
    
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
 


免責聲明!

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



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