上一篇文章(springboot默认日志框架源码解析)从源码阶段分析了springboot的默认日志框架为logback,spring-boot包中,logging.logback下面有默认的日志配置xml;若使用logback做为日志框架,则添加相关配置即可;那么怎么样用log4j2来作为springboot的日志框架呢?为什么要选择log4j2作为日志框架,百度搜索一下就能知道;接下来会从源码入手来分析如何将springboot的日志组件换成log4j2。
还是要从LoggingSystem入手,如下代码所示,我们需要在程序启动前添加参数(第二代码块);这样springboot启动时,就会使用配置的Log4J2LoggingSystem;当然,还需要在grdle中引入log4j2相关的包,否则会报找不到Class的异常;compile "org.apache.logging.log4j:log4j-api:2.14.0";compile "org.apache.logging.log4j:log4j-core:2.14.0" 引用加入gradle
public static LoggingSystem get(ClassLoader classLoader) {
String loggingSystemClassName = System.getProperty(SYSTEM_PROPERTY);
if (StringUtils.hasLength(loggingSystemClassName)) {
if (NONE.equals(loggingSystemClassName)) {
return new NoOpLoggingSystem();
}
return get(classLoader, loggingSystemClassName);
}
LoggingSystem loggingSystem = SYSTEM_FACTORY.getLoggingSystem(classLoader);
Assert.state(loggingSystem != null, "No suitable logging system located");
return loggingSystem;
}
1,启动函数添加system property
public static void main(String[] args) {
System.setProperty("org.springframework.boot.logging.LoggingSystem", "org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");
SpringApplication.run(Bootstrap.class, args);
}
2,添加log4j2.xml到resources目录下,log4j2的名称不能改:注意设置logPath,logFile
/Users/dengyouhua/logs
com-fenghua-util
3,gradle中需要排掉以下包,该方式是gradle直接全局排掉包;单独去排包,会漏排,特别是在使用spring家族的包时
configurations {
compile.exclude module: "spring-boot-starter-logging"
}
到此,你的日志就是真的在使用log4j2打印了。
接下来我们就从源码层面来解释一下,为啥需要上面那么多步;
第一步中,添加系统参数,这个在上一篇以及开头中讲到,要手动设置springboot的日志框架;这样才不会走默认的logback的选项;
第二步添加log4j2.xml的配置,用来配置日志打印的相关参数,这个也没什么好说的。
第三步为什么需要排掉包,org.apache.commons.logging.LogAdapter 这个类决定了必须把spring-boot-starter-logging包给排掉;因为该包会自动引入logback,slf4j相关的包;
重点就是LogAdapter的表态构造里面的逻辑的; 若不排掉上面包,则isPresent(LOG4J_SLF4J_PROVIDER) && isPresent(SLF4J_SPI)必为真,那么logApi,则为LogApi.SLF4J_LAL
private static final String LOG4J_SPI = "org.apache.logging.log4j.spi.ExtendedLogger";
private static final String LOG4J_SLF4J_PROVIDER = "org.apache.logging.slf4j.SLF4JProvider";
private static final String SLF4J_SPI = "org.slf4j.spi.LocationAwareLogger";
private static final String SLF4J_API = "org.slf4j.Logger";
private static final LogApi logApi;
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;
}
}
private LogAdapter() {
}
/**
* Create an actual {@link Log} instance for the selected API.
* @param name the logger name
*/
public static Log createLog(String name) {
switch (logApi) {
case LOG4J:
return Log4jAdapter.createLog(name);
case SLF4J_LAL:
return Slf4jAdapter.createLocationAwareLog(name);
case SLF4J:
return Slf4jAdapter.createLog(name);
default:
return JavaUtilAdapter.createLog(name);
}
}
这样在createLog时,会调用Slf4jAdapter.createLocationAwareLog,该类是LogAdapter的一个内部类,如下所示:
private static class Slf4jAdapter {
public static Log createLocationAwareLog(String name) {
Logger logger = LoggerFactory.getLogger(name);
return (logger instanceof LocationAwareLogger ?
new Slf4jLocationAwareLog((LocationAwareLogger) logger) : new Slf4jLog<>(logger));
}
public static Log createLog(String name) {
return new Slf4jLog<>(LoggerFactory.getLogger(name));
}
}
最终会调用到LoggerFactory中的findPossibleStaticLoggerBinderPathSet方法,该方法加载了一个指定类org/slf4j/impl/StaticLoggerBinder.class;如下所示:
private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
static Set findPossibleStaticLoggerBinderPathSet() {
// use Set instead of list in order to deal with bug #138
// LinkedHashSet appropriate here because it preserves insertion order
// during iteration
Set staticLoggerBinderPathSet = new LinkedHashSet();
try {
ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
Enumeration paths;
if (loggerFactoryClassLoader == null) {
paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
} else {
paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
}
while (paths.hasMoreElements()) {
URL path = paths.nextElement();
staticLoggerBinderPathSet.add(path);
}
} catch (IOException ioe) {
Util.report("Error getting resources from path", ioe);
}
return staticLoggerBinderPathSet;
}
StaticLoggerBinder类中指定了LoggerContext为ch.qos.logback.classic.LoggerContext,所以还是使用的logback为写日志的组件;该类位于org.slf4j.impl,logback-classic包中;
所以第三步排掉包为必须的步骤,否则还是走默认的logback日志框架;
总结:springboot 设置log4j2为日志框架,需要以上三步即可完成;简单总结如下:
1,启动函数添加System.setProperty("org.springframework.boot.logging.LoggingSystem", "org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");
2,添加log4j2.xml的配置
3,添加log4j2的compile "org.apache.logging.log4j:log4j-api:2.14.0";compile "org.apache.logging.log4j:log4j-core:2.14.0"依赖包,并全局排除spring-boot-starter-logging