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