本文是作者原創,版權歸作者所有.若要轉載,請注明出處.文章中若有錯誤和疏漏之處,還請各位大佬不吝指出,謝謝大家.
java日志框架有很多,這篇文章我們來整理一下各大主流的日志框架,
包括log4j logback jul(java.util.logging) jcl(commons-logging) slf4j(simple log facade for java)等常用框架
目前java日志的使用有兩種形式:日志接口和日志實現
1.目前日志接口,常用的有兩種,jcl(commons logging)和slf4j(simple log facade for java)。
2.日志實現目前有這幾類,log4j、jul、logback、log4j2。
我們先從log4j開始
首先,引入maven依賴
<!--log4j--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
然后是配置log4j.properties文件
### 設置###
log4j.rootLogger = debug,stdout,D,E
### 輸出信息到控制抬 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
### 輸出DEBUG 級別以上的日志到=E://logs/error.log ###
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = E://logs/log.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
### 輸出ERROR 級別以上的日志到=E://logs/error.log ###
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File =E://logs/error.log
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
最后是代碼
import org.apache.log4j.Logger; public class Log4j { public static void main(String[] args) { Logger log4j = Logger.getLogger("log4j"); // 記錄debug級別的信息 log4j.debug("This is debug message."); // 記錄info級別的信息 log4j.info("This is info message."); // 記錄error級別的信息 log4j.error("This is error message."); } }
最后是日志信息
好,到這里,log4j就差不多了
,接下來我們看下 jul(java.util.logging) java官方日志jul,位於java.util.logging包下,不用引入依賴,但功能有限,不太常用看下demo
import java.util.logging.Logger; public class JUL { public static void main(String[] args) { //獲取logger實例,相同名的只能生成一個實例 Logger logger = Logger.getLogger("javaLog"); //日志輸出簡寫形式,有不同的級別 logger.warning("warning log"); logger.info("info log"); } }
看下控制台輸出結果,可以看出和log4j相比,日志的時間和顏色明顯不同
好,到這里,jul就差不多了
我們再看jcl(commons logging)是如何整合log4j和jul的
首先還是加入maven依賴,這里我們先加入log4j的依賴
<!--log4j--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <!--jcl--> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency>
看下demo
import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Created by admin on 2019/10/14. */ public class JCL { public static void main(String[] args) { Log log = LogFactory.getLog("JCL"); log.error("Hello World"); } }
看下輸出結果
我們看到,這個好像是log4j的實現,我們把log4j的依賴注釋,重新運行一次,再看下結果
我們發現這次用的好像是jul打印的日志了,下面我們看下源碼,看下jcl的底層是如何實現這種切換的
這里也只貼關鍵代碼,看下getInstance方法
public Log getInstance(String name) throws LogConfigurationException { Log instance = (Log) instances.get(name); if (instance == null) { instance = newInstance(name); instances.put(name, instance); } return instance; }
繼續看newInstance方法
protected Log newInstance(String name) throws LogConfigurationException { Log instance; try { if (logConstructor == null) { instance = discoverLogImplementation(name);//我們看這行代碼 } else { Object params[] = { name }; instance = (Log) logConstructor.newInstance(params); } if (logMethod != null) { Object params[] = { this }; logMethod.invoke(instance, params); } return instance; } catch (LogConfigurationException lce) { // this type of exception means there was a problem in discovery // and we've already output diagnostics about the issue, etc.; // just pass it on throw lce; } catch (InvocationTargetException e) { // A problem occurred invoking the Constructor or Method // previously discovered Throwable c = e.getTargetException(); throw new LogConfigurationException(c == null ? e : c); } catch (Throwable t) { handleThrowable(t); // may re-throw t // A problem occurred invoking the Constructor or Method // previously discovered throw new LogConfigurationException(t); } }
看下上面我注釋的代碼
private Log discoverLogImplementation(String logCategory) throws LogConfigurationException { if (isDiagnosticsEnabled()) { logDiagnostic("Discovering a Log implementation..."); } initConfiguration(); Log result = null; // See if the user specified the Log implementation to use String specifiedLogClassName = findUserSpecifiedLogClassName(); if (specifiedLogClassName != null) { if (isDiagnosticsEnabled()) { logDiagnostic("Attempting to load user-specified log class '" + specifiedLogClassName + "'..."); } result = createLogFromClass(specifiedLogClassName, logCategory, true); if (result == null) { StringBuffer messageBuffer = new StringBuffer("User-specified log class '"); messageBuffer.append(specifiedLogClassName); messageBuffer.append("' cannot be found or is not useable."); // Mistyping or misspelling names is a common fault. // Construct a good error message, if we can informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LOG4J_LOGGER); informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_JDK14_LOGGER); informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LUMBERJACK_LOGGER); informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_SIMPLE_LOGGER); throw new LogConfigurationException(messageBuffer.toString()); } return result; } if (isDiagnosticsEnabled()) { logDiagnostic( "No user-specified Log implementation; performing discovery" + " using the standard supported logging implementations..."); } for(int i=0; i<classesToDiscover.length && result == null; ++i) {//這里就是關鍵代碼 result = createLogFromClass(classesToDiscover[i], logCategory, true); } if (result == null) { throw new LogConfigurationException ("No suitable Log implementation"); } return result; }
看下我注釋的地方,classesToDiscover對象
private static final String[] classesToDiscover = { LOGGING_IMPL_LOG4J_LOGGER, "org.apache.commons.logging.impl.Jdk14Logger", "org.apache.commons.logging.impl.Jdk13LumberjackLogger", "org.apache.commons.logging.impl.SimpleLog" }
這里的Jdk14Logger其實就是jul,我們發現classesToDiscover對象是一個數組,它包含了幾個日志框架的的全限定路徑
回到剛剛的代碼,我看可以看到,jcl就是遍歷這個數組,按順序取值,這里有log4j,就優先log4j,沒有的話,就是要jul框架
我們再看下下面一行代碼,點進去,看看怎么實現的
private Log createLogFromClass(String logAdapterClassName, String logCategory, boolean affectState) throws LogConfigurationException { if (isDiagnosticsEnabled()) { logDiagnostic("Attempting to instantiate '" + logAdapterClassName + "'"); } Object[] params = { logCategory }; Log logAdapter = null; Constructor constructor = null; Class logAdapterClass = null; ClassLoader currentCL = getBaseClassLoader(); for(;;) { // Loop through the classloader hierarchy trying to find // a viable classloader. logDiagnostic("Trying to load '" + logAdapterClassName + "' from classloader " + objectId(currentCL)); try { if (isDiagnosticsEnabled()) { // Show the location of the first occurrence of the .class file // in the classpath. This is the location that ClassLoader.loadClass // will load the class from -- unless the classloader is doing // something weird. URL url; String resourceName = logAdapterClassName.replace('.', '/') + ".class"; if (currentCL != null) { url = currentCL.getResource(resourceName ); } else { url = ClassLoader.getSystemResource(resourceName + ".class"); } if (url == null) { logDiagnostic("Class '" + logAdapterClassName + "' [" + resourceName + "] cannot be found."); } else { logDiagnostic("Class '" + logAdapterClassName + "' was found at '" + url + "'"); } } Class c; try { c = Class.forName(logAdapterClassName, true, currentCL); } catch (ClassNotFoundException originalClassNotFoundException) { // The current classloader was unable to find the log adapter // in this or any ancestor classloader. There's no point in // trying higher up in the hierarchy in this case.. String msg = originalClassNotFoundException.getMessage(); logDiagnostic("The log adapter '" + logAdapterClassName + "' is not available via classloader " + objectId(currentCL) + ": " + msg.trim()); try { // Try the class classloader. // This may work in cases where the TCCL // does not contain the code executed or JCL. // This behaviour indicates that the application // classloading strategy is not consistent with the // Java 1.2 classloading guidelines but JCL can // and so should handle this case. c = Class.forName(logAdapterClassName);//這里通過反射獲取日志實現類的class對象 } catch (ClassNotFoundException secondaryClassNotFoundException) { // no point continuing: this adapter isn't available msg = secondaryClassNotFoundException.getMessage(); logDiagnostic("The log adapter '" + logAdapterClassName + "' is not available via the LogFactoryImpl class classloader: " + msg.trim()); break; } } constructor = c.getConstructor(logConstructorSignature); Object o = constructor.newInstance(params);//這里實例化了日志實現類對象
if (o instanceof Log) {//這里判斷是否是log的實現類
logAdapterClass = c;
logAdapter = (Log) o;//這里將對象傳給logAdapter
break;
}
// Oops, we have a potential problem here. An adapter class
// has been found and its underlying lib is present too, but
// there are multiple Log interface classes available making it
// impossible to cast to the type the caller wanted. We
// certainly can't use this logger, but we need to know whether
// to keep on discovering or terminate now.
//
// The handleFlawedHierarchy method will throw
// LogConfigurationException if it regards this problem as
// fatal, and just return if not.
handleFlawedHierarchy(currentCL, c);
} catch (NoClassDefFoundError e) {
// We were able to load the adapter but it had references to
// other classes that could not be found. This simply means that
// the underlying logger library is not present in this or any
// ancestor classloader. There's no point in trying higher up
// in the hierarchy in this case..
String msg = e.getMessage();
logDiagnostic("The log adapter '" + logAdapterClassName +
"' is missing dependencies when loaded via classloader " + objectId(currentCL) +
": " + msg.trim());
break;
} catch (ExceptionInInitializerError e) {
// A static initializer block or the initializer code associated
// with a static variable on the log adapter class has thrown
// an exception.
//
// We treat this as meaning the adapter's underlying logging
// library could not be found.
String msg = e.getMessage();
logDiagnostic("The log adapter '" + logAdapterClassName +
"' is unable to initialize itself when loaded via classloader " + objectId(currentCL) +
": " + msg.trim());
break;
} catch (LogConfigurationException e) {
// call to handleFlawedHierarchy above must have thrown
// a LogConfigurationException, so just throw it on
throw e;
} catch (Throwable t) {
handleThrowable(t); // may re-throw t
// handleFlawedDiscovery will determine whether this is a fatal
// problem or not. If it is fatal, then a LogConfigurationException
// will be thrown.
handleFlawedDiscovery(logAdapterClassName, currentCL, t);
}
if (currentCL == null) {
break;
}
// try the parent classloader
// currentCL = currentCL.getParent();
currentCL = getParentClassLoader(currentCL);
}
if (logAdapterClass != null && affectState) {
// We've succeeded, so set instance fields
this.logClassName = logAdapterClassName;
this.logConstructor = constructor;
// Identify the <code>setLogFactory</code> method (if there is one)
try {
this.logMethod = logAdapterClass.getMethod("setLogFactory", logMethodSignature);
logDiagnostic("Found method setLogFactory(LogFactory) in '" + logAdapterClassName + "'");
} catch (Throwable t) {
handleThrowable(t); // may re-throw t
this.logMethod = null;
logDiagnostic("[INFO] '" + logAdapterClassName + "' from classloader " + objectId(currentCL) +
" does not declare optional method " + "setLogFactory(LogFactory)");
}
logDiagnostic("Log adapter '" + logAdapterClassName + "' from classloader " +
objectId(logAdapterClass.getClassLoader()) + " has been selected for use.");
}
return logAdapter;//返回此對象
}
我們可以看到底層是用了反射獲取的對象,截個圖
我們可以看到,這里是jcl包里的log4j.
好了,到這里jcl就差不多了,我們從源碼可以看出jcl是如何切換日志框架的,
接下來學習下目前流行的slf4j,它支持所有主流的日志實現框架,非常強大,推薦使用
老規矩,添加依賴
<!--slf4j--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.25</version> </dependency>
看下demo
import org.slf4j.LoggerFactory; import org.slf4j.Logger; /** * Created by admin on 2019/10/14. */ public class Slf4j { public static void main(String[] args) { Logger logger= LoggerFactory.getLogger("slf4j"); logger.error("Hello World"); } }
看下控制台
哎,好像沒打印,這里可以看出,只引入slf4j是不打印日志的,我們可以看下官網http://www.slf4j.org/
Simple Logging Facade for Java (SLF4J) 它是簡單日志門面,不自己實現
好,我們再引入log4j的綁定器依賴
<!--log4j的綁定器--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.25</version> </dependency>
,再運行一次,看下結果
成了,如何我們要切換日志呢,注釋掉log4j,試試jul吧
<!--log4j的綁定器--> <!--<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.25</version> </dependency>--> <!--jul的綁定器--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-jdk14</artifactId> <version>1.7.25</version> </dependency>
再看下控制台
成功了,這就是推薦使用slf4j的原因,切換日志框架非常方便
注意,綁定器有且只能有一個
最后,看下logback的使用吧
添加依賴
<!-- logback --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.1.11</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.11</version> </dependency>
logback,xml
<?xml version="1.0" encoding="UTF-8"?> <!-- scan:當此屬性設置為true時,配置文件如果發生改變,將會被重新加載,默認值為true。 scanPeriod:設置監測配置文件是否有修改的時間間隔,如果沒有給出時間單位,默認單位是毫秒當scan為true時,此屬性生效。默認的時間間隔為1分鍾。 debug:當此屬性設置為true時,將打印出logback內部日志信息,實時查看logback運行狀態。默認值為false。 --> <configuration debug="false" scan="true" scanperiod="1800 seconds"> <!--當前logger上下文名稱--> <contextName>logbackStudy</contextName> <!--當前日期--> <timestamp key="nowDate" datePattern="yyyyMMdd" /> <!--輸出到控制台--> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%date %level %logger : %msg %n</pattern> </encoder> </appender> <!--輸出到文件--> <appender name="FILE" class="ch.qos.logback.core.FileAppender"> <file>logbackstudy_${nowDate}.log</file> <!--日志滾動的策略,按時間歸檔,實現了RollingPolicy和TriggeringPolicy接口,RollingPolicy指歷史文件歸檔策略,TriggeringPolicy指歷史文件歸檔時機--> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>logbackStudy_%d{yyyyMMdd}.log.gz</fileNamePattern> <!--最多保存30天歷史--> <maxHistory>30</maxHistory> <!--日志文件合起來最大1G,超出后會刪除舊日志--> <totalSizeCap>1G</totalSizeCap> </rollingPolicy> <encoder> <!--日志模板--> <pattern>%date %level %logger : %msg %n</pattern> </encoder> </appender> <!--控制指定包或類的日志輸出(包括等級和目的地), additivity表示日志信息是否向上傳遞,false為不傳遞(即不重復打印)--> <logger name="com.dragon.study.log.Slf4jAndLogbackMainTwo" level="warn" additivity="false"> <!--可多個appender--> <appender-ref ref="STDOUT" /> </logger> <root level="info"> <appender-ref ref="STDOUT" /> <appender-ref ref="FILE" /> </root> </configuration>
demo實例
import org.slf4j.LoggerFactory; import org.slf4j.Logger; /** * Created by admin on 2019/10/14. */ public class Slf4j { public static void main(String[] args) { Logger logger= LoggerFactory.getLogger("slf4j"); logger.error("Hello World"); } }
控制台
輸出的文件
總結:目前來說日志框架推薦使用slf4j日志接口+一個你熟悉的日志實現,這樣可以直接切換日志的依賴,不用改已有的代碼