JAVA日志框架
JAVA有好多優(yōu)秀的日志框架,比如log4j、log4j2、logback、JUL(java.util.logging)、JCL(JAVA Common Logging)等等,logback是后起之秀,是Spring Boot默認(rèn)日志框架。
今天文章的目標(biāo)不是研究JAVA的這些日志框架,而是在應(yīng)用中處于他們前面的日志門面SLF4J,以及初步了解一下Spring Boot的默認(rèn)日志框架是在什么地方配置的、怎么替換Spring Boot默認(rèn)的日志框架。
SLF4J
SFL4J,全名Simple Logging Facade for Java,意思是簡(jiǎn)單JAVA日志門面,是Facade設(shè)計(jì)模式的一個(gè)典型實(shí)現(xiàn)。
SFL4J本身并沒有日志的任何實(shí)現(xiàn),它只是一個(gè)日志門面,目的是為了讓應(yīng)用層能夠簡(jiǎn)單的、方便的使用以上提到的各種不同的日志框架,在代碼層不做任何改動(dòng)的情況下,在最終部署的時(shí)候決定具體使用哪一個(gè)日志框架。沖這一點(diǎn),SLF4J就非常NB,能允許程序員在編碼的時(shí)候不考慮具體使用哪一個(gè)日志框架、部署的時(shí)候不需要修改一行代碼、隨意選擇日志框架。而且,雖然不是很有必要,但是只要你高興,應(yīng)用運(yùn)行的過程中你都可以隨時(shí)切換日志框架,比如你剛開始選擇log4j2,但是后來覺得不爽想要換成logback,只要你在開發(fā)的時(shí)候使用了SLF4J,你就可以任性地隨時(shí)切換。
使用SLF4J
嚴(yán)格來說,SLF4J只需要一個(gè)包:slf4j-api-xxx.jar(xxx是版本號(hào)) 就可以使用,POM文件中引入:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
之后:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
logger.info("Hello World");
}
}
應(yīng)用classpath下有任何前述日志框架存在的情況下,以上代碼中的日志打印語句就會(huì)根據(jù)日志框架的配置輸出到控制臺(tái)或者日志文件中了。
SLF4J API及jar包框架
上述案例可以發(fā)現(xiàn),原則上來說,項(xiàng)目Pom文件引入slf4j-api之后,SLF4J就可以正常工作了,但是SLF4J可以正常工作,并不代表整個(gè)日志系統(tǒng)可以正常工作,因?yàn)镾LF4J只是日志門面,需要綁定后端的日志框架之后,日志系統(tǒng)才能正常工作。
我們先看一下SLF4J官網(wǎng)對(duì)于SLF4J API的框架圖:
可以看出,如果我們采用SLF4J本家的日志框架作為我們應(yīng)用的日志框架的話,比如用logback的話,我們只需要slf4j-api.jar + logback相關(guān)的jar包就可以了,或者我們用slf4j自帶的simple日志框架的話,也只需要slf4j-api.jar + slf4j-simple.jar。
但是如果用其他的三方日志框架,比如log4j、或者JUL的話,由于三方框架比如log4j早在SLF4J出現(xiàn)之前就已經(jīng)占據(jù)JAVA日志的半壁江山了,所以早期版本的日志框架不可能適應(yīng)后面才出現(xiàn)的SLF4J,所以只能SLF4J自己想辦法來適應(yīng)。
SLF4J給出的解決方案就是針對(duì)各日志框架的API:
slf4j-log4j12-2.0.10.jar: 為綁定log4j version 1.2,這是一個(gè)很古老的版本了,早已宣布?jí)劢K正寢了,但是考慮到有些老系統(tǒng)可能還在使用log4j 1.x,所以提供了這個(gè)jar包以便兼容。其實(shí)從SLF4J 1.7.35之后,slf4j-log4j模塊的調(diào)用就已經(jīng)在編譯器直接導(dǎo)航到slf4j-reload4j的調(diào)用了,所以這個(gè)模塊對(duì)于新版本的SLF4J來說幾乎沒用了。
slf4j-reload4j-2.0.10.jar:后期版本的SLF4J綁定log4j日志框架,同時(shí)需要reload4j.jar(log4j 1.2x之后的替代品)。
slf4j-jdk14-2.0.10.jar: SLF4J綁定JUL(java14之后的內(nèi)置日志框架)日志框架,同時(shí)需要JDK14。
slf4j-nop-2.0.10.jar: 應(yīng)用不綁定任何日志框架的情況下的SLF4J的默認(rèn)NOP實(shí)現(xiàn),啥也沒干,只是在應(yīng)用運(yùn)行進(jìn)行日志框架綁定的時(shí)候不報(bào)錯(cuò)。
slf4j-simple-2.0.10.jar: SLF4J自己實(shí)現(xiàn)的一個(gè)簡(jiǎn)單的、輕量級(jí)日志框架,應(yīng)該沒人用。
slf4j-jcl-2.0.10.jar: 綁定Apache Commons Logging。
logback-classic-xxx.jar: logback日志框架,同時(shí)需要logback-core-xxx.jar,可直接支持SLF4J。
將以上jar包放入或移除當(dāng)前應(yīng)用(或者通過pom文件)后,就可以輕而易舉的實(shí)現(xiàn)日志框架的更換,不需要你動(dòng)一行代碼,這就是SLF4J的魔力。
不綁定
如果不引入任何日志框架、代碼中又使用了SLF4J的話,會(huì)是什么情況?
如果版本是SLF4J 1.6.0 之前的版本,沒有加載任何日志框架的情況下,SLF4J在綁定日志框架的時(shí)候會(huì)拋出異常:NoClassDefFoundError 。因?yàn)榻壎ㄈ罩究蚣艿臅r(shí)候找不到org.slf4j.impl.StaticLoggerBinder類。
之后的、SLF4J2.0之前的版本,控制臺(tái)會(huì)打?。?/p>
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See https://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
SLF4J2.0以上,控制臺(tái)會(huì)打印:
SLF4J: No SLF4J providers were found.
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See https://www.slf4j.org/codes.html#noProviders for further details.
SLF4J 1.6.0之后,如果你的項(xiàng)目并沒有加載日志框架,SLF4J提供了一個(gè)日志啞實(shí)現(xiàn),叫no-operation (NOP) logger implementation,這個(gè)NOP其實(shí)就是個(gè)假把式,所有的日志打印操作都不會(huì)得到任何輸出,NOP啥都不干。
橋接API
如果你的已有應(yīng)用沒有使用SLF4J、而是使用JCL或log4j等JAVA日志框架,SLF4J非常貼心的為你提供了兩種方案、讓你能夠以最小代價(jià)將as-is系統(tǒng)的日志框架切換到SLF4J上來:
- Bridging legacy APIs:橋接API
- slf4j-migrator:代碼遷移工具
橋接API圖:
通過橋接API,允許你將既有系統(tǒng)的基于log4j、JCL、JUL等日志框架的系統(tǒng)遷移到SLF4J上來。原理是:jcl-over-slf4j.jar替換原有的commons-logging.jar,應(yīng)用中對(duì)原來的JCL日志框架的調(diào)用接口都被替換成了jcl-over-slf4j.jar中的橋接接口,jcl-over-slf4j.jar橋接接口會(huì)將日志接口API重新定向到SLF4J中、從而納入到SLF4J體系中來。
slf4j-migrator代碼遷移工具是直接對(duì)你的代碼動(dòng)手術(shù)的,支持JCL、log4j、JUL的遷移:
Spring Boot默認(rèn)日志框架
Spring Boot的默認(rèn)日志框架是在spring-boot-starter中指定的,spring-boot-starter中包含了spring-boot-starter-logging:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<version>3.1.4</version>
<scope>compile</scope>
</dependency>
而spring-boot-starter-logging中引入了logback:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.11</version>
<scope>compile</scope>
</dependency>
所以,Spring Boot的默認(rèn)日志框架是logback。
如果想要替換默認(rèn)的日志框架,比如換成log4j2,首先需要在pom文件中加入依賴:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-reload4j</artifactId>
<version>2.0.10</version>
</dependency>
之后直接啟動(dòng)應(yīng)用,發(fā)現(xiàn):
這個(gè)信息是在應(yīng)用系統(tǒng)的早期就打印出來了,說明日志框架的加載在Spring Boot啟動(dòng)過程中是比較早的。
然后,logback也可以正常工作了(代碼中加入log可以檢查一下底層日志框架到底是啥):
public HelloWorldController(){
log.info("---1=2---");
log.info(log.getClass().getName());
}
但是我們想要替換logback為log4j2的目標(biāo)卻沒有實(shí)現(xiàn)!
原因在上面第一張圖里已經(jīng)說了,SLF4J在classpath下發(fā)現(xiàn)了兩個(gè)provider(我們用的是SLF4J2.x版本,通過provider綁定底層日志框架)。
發(fā)現(xiàn)兩個(gè)provider的話,SLF4J會(huì)依賴JVM隨機(jī)綁定一個(gè),我們測(cè)試的這個(gè)案例是綁定了logback。
怎么能讓它綁定log4j呢?
其實(shí)通過SLF4J的源碼發(fā)現(xiàn)了一種方法,就是指定環(huán)境變量:
System property for explicitly setting the provider class. If set and the provider could be instantiated, then the service loading mechanism will be bypassed.
Since:2.0.9
如果指定了這個(gè)環(huán)境變量,SLF4J的加載機(jī)制就會(huì)被跳過而直接加載指定的provider,比如:
從代碼看這個(gè)設(shè)置也是應(yīng)該能生效的:
static List<SLF4JServiceProvider> findServiceProviders() {
List<SLF4JServiceProvider> providerList = new ArrayList<>();
// retain behaviour similar to that of 1.7 series and earlier. More specifically, use the class loader that
// loaded the present class to search for services
final ClassLoader classLoaderOfLoggerFactory = LoggerFactory.class.getClassLoader();
SLF4JServiceProvider explicitProvider = loadExplicitlySpecified(classLoaderOfLoggerFactory);
if(explicitProvider != null) {
providerList.add(explicitProvider);
return providerList;
}
loadExplicitlySpecified方法:
static SLF4JServiceProvider loadExplicitlySpecified(ClassLoader classLoader) {
String explicitlySpecified = System.getProperty(PROVIDER_PROPERTY_KEY);
if (null == explicitlySpecified || explicitlySpecified.isEmpty()) {
return null;
}
try {
String message = String.format("Attempting to load provider \"%s\" specified via \"%s\" system property", explicitlySpecified, PROVIDER_PROPERTY_KEY);
Util.report(message);
Class<?> clazz = classLoader.loadClass(explicitlySpecified);
Constructor<?> constructor = clazz.getConstructor();
Object provider = constructor.newInstance();
return (SLF4JServiceProvider) provider;
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
String message = String.format("Failed to instantiate the specified SLF4JServiceProvider (%s)", explicitlySpecified);
Util.report(message, e);
return null;
} catch (ClassCastException e) {
String message = String.format("Specified SLF4JServiceProvider (%s) does not implement SLF4JServiceProvider interface", explicitlySpecified);
Util.report(message, e);
return null;
}
}
如果指定了這個(gè)系統(tǒng)參數(shù)的話,就直接通過classloader實(shí)例化這個(gè)provider…但是確實(shí)沒有測(cè)試成功,暫時(shí)沒找到原因。
我們還有另外一個(gè)啟用log4j2、停用logback的方案,pom文件排除logback:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</exclusion>
</exclusions>
</dependency>
之后刷新pom,發(fā)現(xiàn)logback的引用已經(jīng)消失了。
刷新前:
刷新后:
啟動(dòng)應(yīng)用:
就會(huì)發(fā)現(xiàn)Spring Boot項(xiàng)目使用log4j就沒有用logback那么舒服了,log4j是需要配置的。隨便放一個(gè)log4j.properties配置文件就可以正常工作了。
@Slf4j注解
@Slf4j是lombok的一個(gè)注解,所以你就能知道他其實(shí)沒啥,語法糖而已,幫著你在當(dāng)前類生成一個(gè):文章來源:http://www.zghlxwxcb.cn/news/detail-795773.html
private static final org.slf4j.Logger log =org.slf4j.LoggerFactory.getLogger(Youclass.class);
Ohhh…OK文章來源地址http://www.zghlxwxcb.cn/news/detail-795773.html
到了這里,關(guān)于SLF4J & Spring Boot日志框架的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!