springboot换log4j2写日志源码分析
上一篇文章(springboot默认日志框架选择解析)从源码阶段分析了springboot的默认日志框架为logback,spring-boot包中,logging.logback下面有默认的日志配置xml;若使用logback做为日志框架,则添加相关配置即可;那么怎么样用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
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Properties>
<property name="logPath">/Users/dengyouhua/logs</property>
<property name="logFile">com-fenghua-util</property>
</Properties>
<Appenders>
<Console name="console_log" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<RollingFile name="file_log" fileName="${logPath}/${logFile}.log" append="true"
filePattern="${logPath}/${logFile}-%d{yyyy-MM-dd}-%i.log">
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="500MB"/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Root level="debug">
<AppenderRef ref="file_log"/>
<AppenderRef ref="console_log"/>
</Root>
</Loggers>
</Configuration>
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<URL> findPossibleStaticLoggerBinderPathSet() {
// use Set instead of list in order to deal with bug #138
// LinkedHashSet appropriate here because it preserves insertion order
// during iteration
Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
try {
ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
Enumeration<URL> 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



浙公网安备 33010602011771号