一、日志框架
1)目前主流的日志框架有:log4j1,log4j2,logback,jul(JDK内部实现的日志框架)
日志框架那么多,我们平时写代码的时候,难道要硬编码某一种具体的日式框架?由此引出JCL 和 SLF4J。
2)JCL:全称为Jakarta Commons Logging,是Apache
提供通用日志的API,其原理如下:
内部维护了一个数组,通过反射去获取具体的日志实现框架,加载顺序是按照数组顺序来的,因此,一旦找到log4j,那么就会使用log4j,没找到就使用JDK提供的日志框架jul。
其缺点也很明显,在代码里直接写死了4种日志框架,其他日志框架,无法使用
private static final String[] classesToDiscover =
new String[]{"org.apache.commons.logging.impl.Log4JLogger",
"org.apache.commons.logging.impl.Jdk14Logger",
"org.apache.commons.logging.impl.Jdk13LumberjackLogger",
"org.apache.commons.logging.impl.SimpleLog"};
3)SLF4J 一套抽象的日志API,其原理如下:
SLF4J需要使用绑定器,绑定到具体的日志实现框架,才能完成日志打印。
如果一个项目中,一个模块使用了log4j1 ,另外一个模块使用了slf4j+logback绑定器,那项目中就会有两种日志格式。
如何解决这个问题呢?使用桥接器,把log4j1的日志桥接到slf4j,这样打印格式都会统一到logback。
二、Spring日志
Spring4使用的是JCL,Spring5使用的是spring-jcl(因为JCL不维护了,因此spring把源码搞过来,自己封装成了一个模块)
spring-jcl 原理如下:
static {
if (isPresent(LOG4J_SPI)) {
if (isPresent(LOG4J_SLF4J_PROVIDER) && isPresent(SLF4J_SPI)) {
// log4j-to-slf4j bridge -> we'll rather go with the SLF4J SPI;
// however, we still prefer Log4j over the plain SLF4J API since
// the latter does not have location awareness support.
logApi = LogApi.SLF4J_LAL;
}
else {
// Use Log4j 2.x directly, including location awareness support
logApi = LogApi.LOG4J;
}
}
else if (isPresent(SLF4J_SPI)) {
// Full SLF4J SPI including location awareness support
logApi = LogApi.SLF4J_LAL;
}
else if (isPresent(SLF4J_API)) {
// Minimal SLF4J API without location awareness support
logApi = LogApi.SLF4J;
}
else {
// java.util.logging as default
logApi = LogApi.JUL;
}
}
从上面我们可以看出,Spring5.* 比较偏爱log4j2这个日志框架。
三、SpringBoot日志
SpringBoot集成各种开源组件, 是如何做到统一使用logback来打印日志呢?
SpringBoot 通过引入log4j2桥接器(log4j-to-slf4j),JUL桥接器(jul-to-slf4j),将其他的开源组件使用到的日志(log4j2,jul)桥接到sl4fj
四、Mybatis日志
通过源码可以发现,Mybatis日志处理方式,也是顺序去加载日志框架,加载到哪个就使用哪个,跟JCL框架比较像。
而且我们可以发现,Mybatis是比较偏爱slf4j,一旦找到了slf4j就不会再去找其他的日志框架了。
package org.apache.ibatis.logging;
import java.lang.reflect.Constructor;
import org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl;
import org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl;
import org.apache.ibatis.logging.log4j.Log4jImpl;
import org.apache.ibatis.logging.log4j2.Log4j2Impl;
import org.apache.ibatis.logging.nologging.NoLoggingImpl;
import org.apache.ibatis.logging.slf4j.Slf4jImpl;
import org.apache.ibatis.logging.stdout.StdOutImpl;
public final class LogFactory {
public static final String MARKER = "MYBATIS";
private static Constructor<? extends Log> logConstructor;
private LogFactory() {
}
public static Log getLog(Class<?> clazz) {
return getLog(clazz.getName());
}
public static Log getLog(String logger) {
try {
return (Log)logConstructor.newInstance(logger);
} catch (Throwable var2) {
throw new LogException("Error creating logger for logger " + logger + ". Cause: " + var2, var2);
}
}
public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
setImplementation(clazz);
}
public static synchronized void useSlf4jLogging() {
setImplementation(Slf4jImpl.class);
}
public static synchronized void useCommonsLogging() {
setImplementation(JakartaCommonsLoggingImpl.class);
}
public static synchronized void useLog4JLogging() {
setImplementation(Log4jImpl.class);
}
public static synchronized void useLog4J2Logging() {
setImplementation(Log4j2Impl.class);
}
public static synchronized void useJdkLogging() {
setImplementation(Jdk14LoggingImpl.class);
}
public static synchronized void useStdOutLogging() {
setImplementation(StdOutImpl.class);
}
public static synchronized void useNoLogging() {
setImplementation(NoLoggingImpl.class);
}
private static void tryImplementation(Runnable runnable) {
if (logConstructor == null) {
try {
runnable.run();
} catch (Throwable var2) {
}
}
}
private static void setImplementation(Class<? extends Log> implClass) {
try {
Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
Log log = (Log)candidate.newInstance(LogFactory.class.getName());
if (log.isDebugEnabled()) {
log.debug("Logging initialized using '" + implClass + "' adapter.");
}
logConstructor = candidate;
} catch (Throwable var3) {
throw new LogException("Error setting Log implementation. Cause: " + var3, var3);
}
}
static {
tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}
}
五、其他
1)各种日志框架的绑定器和桥接器
注:绑定器一般已经包含了SLF4J和具体的日志实现框架,因此引入绑定器之后,不需要再显式映入SLF4J和具体的日志框架。
2)log4j1 的桥接器和log4j1不能同时存在,因为log4j1的桥接器,对log4j1 的源码做了修改,同时引入,会报错。