spring boot 源码解析28-Log4J2LoggingSystem

xiaoxiao2021-02-28  26

前言

spring boot 中关于Log的实现我们已经分析了JavaLoggingSystem,本文就来看看Log4J2LoggingSystem,在分析之前,我们需要先分析一下Slf4JLoggingSystem–> Log4J2LoggingSystem,LogbackLoggingSystem 的父类.

解析

Slf4JLoggingSystem

字段,构造器如下:

private static final String BRIDGE_HANDLER = "org.slf4j.bridge.SLF4JBridgeHandler"; public Slf4JLoggingSystem(ClassLoader classLoader) { super(classLoader); }

覆写了如下方法:

beforeInitialize,代码如下:

public void beforeInitialize() { super.beforeInitialize(); // 1. 配置SLF4JBridgeHandler configureJdkLoggingBridgeHandler(); }

configureJdkLoggingBridgeHandler–>配置SLF4JBridgeHandler 代码如下:

private void configureJdkLoggingBridgeHandler() { try { // 1. 如果在当前类路径下存在org.slf4j.bridge.SLF4JBridgeHandler,则 if (isBridgeHandlerAvailable()) { // 1.1 删除slf4j 中root logger 配置的所有handler removeJdkLoggingBridgeHandler(); // 1.2 为root logger添加SLF4JBridgeHandler SLF4JBridgeHandler.install(); } } catch (Throwable ex) { // Ignore. No java.util.logging bridge is installed. } }

如果在当前类路径下存在org.slf4j.bridge.SLF4JBridgeHandler,则

删除slf4j 中root logger 配置的所有handler,代码如下:

private void removeJdkLoggingBridgeHandler() { try { if (isBridgeHandlerAvailable()) { try { SLF4JBridgeHandler.removeHandlersForRootLogger(); } catch (NoSuchMethodError ex) { // Method missing in older versions of SLF4J like in JBoss AS 7.1 SLF4JBridgeHandler.uninstall(); } } } catch (Throwable ex) { // Ignore and continue } }

为root logger添加SLF4JBridgeHandler,代码如下:

public static void install() { LogManager.getLogManager().getLogger("").addHandler(new SLF4JBridgeHandler()); }

cleanUp–>删除slf4j 中root logger 配置的所有handler,代码如下:

public void cleanUp() { removeJdkLoggingBridgeHandler(); }

loadConfiguration–>设置系统属性,代码如下:

protected void loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile) { Assert.notNull(location, "Location must not be null"); if (initializationContext != null) { applySystemProperties(initializationContext.getEnvironment(), logFile); } }

调用父类(AbstractLoggingSystem)中的 applySystemProperties方法,代码如下:

protected final void applySystemProperties(Environment environment, LogFile logFile) { new LoggingSystemProperties(environment).apply(logFile); }

Log4J2LoggingSystem

字段如下:

private static final String FILE_PROTOCOL = "file"; private static final LogLevels<Level> LEVELS = new LogLevels<Level>(); static { LEVELS.map(LogLevel.TRACE, Level.TRACE); LEVELS.map(LogLevel.DEBUG, Level.DEBUG); LEVELS.map(LogLevel.INFO, Level.INFO); LEVELS.map(LogLevel.WARN, Level.WARN); LEVELS.map(LogLevel.ERROR, Level.ERROR); LEVELS.map(LogLevel.FATAL, Level.FATAL); LEVELS.map(LogLevel.OFF, Level.OFF); } // 在beforeInitialize中添加该filter,目的是log4j2 没有初始化完毕时是不能使用的,此时所有的处理都是DENY,就不会打印日志了 private static final Filter FILTER = new AbstractFilter() { @Override public Result filter(LogEvent event) { return Result.DENY; } @Override public Result filter(Logger logger, Level level, Marker marker, Message msg, Throwable t) { return Result.DENY; } @Override public Result filter(Logger logger, Level level, Marker marker, Object msg, Throwable t) { return Result.DENY; } @Override public Result filter(Logger logger, Level level, Marker marker, String msg, Object... params) { return Result.DENY; } }; public Log4J2LoggingSystem(ClassLoader classLoader) { super(classLoader); }

方法如下:

getStandardConfigLocations –> 获取配置文件,代码如下:

protected String[] getStandardConfigLocations() { return getCurrentlySupportedConfigLocations(); }

调用:

private String[] getCurrentlySupportedConfigLocations() { List<String> supportedConfigLocations = new ArrayList<String>(); // 1. 如果当前类路径下存在com.fasterxml.jackson.dataformat.yaml.YAMLParser,则加入log4j2.yaml,log4j2.yml,默认情况下不支持 if (isClassAvailable("com.fasterxml.jackson.dataformat.yaml.YAMLParser")) { Collections.addAll(supportedConfigLocations, "log4j2.yaml", "log4j2.yml"); } // 2. 如果当前类路径下存在com.fasterxml.jackson.databind.ObjectMapper,则加入log4j2.json,log4j2.jsn if (isClassAvailable("com.fasterxml.jackson.databind.ObjectMapper")) { Collections.addAll(supportedConfigLocations, "log4j2.json", "log4j2.jsn"); } // 3. 默认加入log4j2.xml supportedConfigLocations.add("log4j2.xml"); return supportedConfigLocations .toArray(new String[supportedConfigLocations.size()]); } 如果当前类路径下存在com.fasterxml.jackson.dataformat.yaml.YAMLParser,则加入log4j2.yaml,log4j2.yml,默认情况下不支持如果当前类路径下存在com.fasterxml.jackson.databind.ObjectMapper,则加入log4j2.json,log4j2.jsn默认加入log4j2.xml

因此,默认情况下返回的是log4j2.json, log4j2.jsn, log4j2.xml

beforeInitialize,代码如下:

public void beforeInitialize() { LoggerContext loggerContext = getLoggerContext(); if (isAlreadyInitialized(loggerContext)) { return; } super.beforeInitialize(); loggerContext.getConfiguration().addFilter(FILTER); } 获得LoggerContext,如果LoggerContext中的ExternalContext 存放的是org.springframework.boot.logging.LoggingSystem,则意味着已经初始化过了,此时直接return调用父类的beforeInitialize,配置SLF4JBridgeHandler添加FILTER,目的是log4j2 没有初始化完毕时是不能使用的,此时所有的处理都是DENY,就不会打印日志了

initialize,代码如下:

public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) { // 1. 获得LoggerContext,如果LoggerContext中的ExternalContext 存放的是org.springframework.boot.logging.LoggingSystem, // 则意味着已经初始化过了,此时直接return LoggerContext loggerContext = getLoggerContext(); if (isAlreadyInitialized(loggerContext)) { return; } // 2. 删除FILTER,此时意味着已经初始化成功了 loggerContext.getConfiguration().removeFilter(FILTER); // 3. 加载配置文件 super.initialize(initializationContext, configLocation, logFile); // 4. 向LoggerContext中的ExternalContext 存放-->org.springframework.boot.logging.LoggingSystem,标记成功初始化 markAsInitialized(loggerContext); } 获得LoggerContext,如果LoggerContext中的ExternalContext 存放的是org.springframework.boot.logging.LoggingSystem,则意味着已经初始化过了,此时直接return删除FILTER,此时意味着已经初始化成功了加载配置文件

向LoggerContext中的ExternalContext 存放–>org.springframework.boot.logging.LoggingSystem,标记成功初始化.代码如下:

private void markAsInitialized(LoggerContext loggerContext) { loggerContext.setExternalContext(LoggingSystem.class.getName()); }

loadDefaults–>加载默认的配置文件:

protected void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile) { // 1. 如果logFile 不等于null,则加载org/springframework/boot/logging/log4j2/log4j2-file.xml的配置,默认这步是不会执行的 if (logFile != null) { loadConfiguration(getPackagedConfigFile("log4j2-file.xml"), logFile); } // 否则,加载org/springframework/boot/logging/log4j2/log4j2.xml else { loadConfiguration(getPackagedConfigFile("log4j2.xml"), logFile); } } 如果logFile 不等于null,则加载org/springframework/boot/logging/log4j2/log4j2-file.xml的配置,默认这步是不会执行的否则,加载org/springframework/boot/logging/log4j2/log4j2.xml

loadConfiguration实现如下:

protected void loadConfiguration(String location, LogFile logFile) { Assert.notNull(location, "Location must not be null"); try { // 1. 获得LoggerContext LoggerContext ctx = getLoggerContext(); // 2.将location转换为URL URL url = ResourceUtils.getURL(location); // 3.根据url 获得对应的ConfigurationSource ConfigurationSource source = getConfigurationSource(url); // 4. 启动 ctx.start(ConfigurationFactory.getInstance().getConfiguration(ctx, source)); } catch (Exception ex) { throw new IllegalStateException( "Could not initialize Log4J2 logging from " + location, ex); } } 获得LoggerContext将location转换为URL

根据url 获得对应的ConfigurationSource.代码如下:

private ConfigurationSource getConfigurationSource(URL url) throws IOException { InputStream stream = url.openStream(); if (FILE_PROTOCOL.equals(url.getProtocol())) {// 如果是file协议 return new ConfigurationSource(stream, ResourceUtils.getFile(url)); } // 2. 其他,由于当前是classpath 协议,因此会执行到这里 return new ConfigurationSource(stream, url); }启动

reinitialize –>调用时机:在log4j初始化时,在类路径下加载的以下任一1个配置文件:log4j2.json, log4j2.jsn, log4j2.xml,通过LogFile等于null,则调用该方法.代码如下:

protected void reinitialize(LoggingInitializationContext initializationContext) { getLoggerContext().reconfigure(); }

log4j 不会直接删除所有的Loggers在重新配置的阶段,而是会重新创建LoggerConfig,然后进行替换.旧的LoggerConfig,Appenders,Filters 会被释放

setLogLevel.代码如下:

public void setLogLevel(String loggerName, LogLevel logLevel) { // 1. 转换LogLevel 为log4j2的LogLevel Level level = LEVELS.convertSystemToNative(logLevel); // 2.根据loggerName 获得对应的LoggerConfig LoggerConfig loggerConfig = getLoggerConfig(loggerName); // 3. 如果不存在对应的LoggerConfig,则进行添加,否则直接进行修改 if (loggerConfig == null) { loggerConfig = new LoggerConfig(loggerName, level, true); getLoggerContext().getConfiguration().addLogger(loggerName, loggerConfig); } else { loggerConfig.setLevel(level); } // 4. 更新 getLoggerContext().updateLoggers(); } 转换LogLevel 为log4j2的LogLevel根据loggerName 获得对应的LoggerConfig如果不存在对应的LoggerConfig,则进行添加,否则直接进行修改更新

getLoggerConfigurations –> 获得所有的配置文件.代码如下:

public List<LoggerConfiguration> getLoggerConfigurations() { List<LoggerConfiguration> result = new ArrayList<LoggerConfiguration>(); Configuration configuration = getLoggerContext().getConfiguration(); for (LoggerConfig loggerConfig : configuration.getLoggers().values()) { result.add(convertLoggerConfiguration(loggerConfig)); } Collections.sort(result, CONFIGURATION_COMPARATOR); return result; } 获得Configuration中配置的Logger,遍历之

将LoggerConfig 转换为LoggerConfiguration.代码如下:

private LoggerConfiguration convertLoggerConfiguration(LoggerConfig loggerConfig) { if (loggerConfig == null) { return null; } LogLevel level = LEVELS.convertNativeToSystem(loggerConfig.getLevel()); String name = loggerConfig.getName(); if (!StringUtils.hasLength(name) || LogManager.ROOT_LOGGER_NAME.equals(name)) { name = ROOT_LOGGER_NAME; } return new LoggerConfiguration(name, level, level); } 如果LoggerConfig等于null,则返回null将log4j配置的日志级别转换为LogLevel如果logger的名字等于root,或者不存在,则将其赋值为root实例化LoggerConfiguration排序–>将root logger 排在第1位,其他的按照字典顺序排序

getShutdownHandler–>在ShutdownHandler中直接调用LoggerContext的stop方法.代码如下:

public Runnable getShutdownHandler() { return new ShutdownHandler(); }

ShutdownHandler 代码如下:

private final class ShutdownHandler implements Runnable { @Override public void run() { getLoggerContext().stop(); } }

cleanUp–>调用时机,当spring boot 发出ContextClosedEvent事件时调用,代码如下:

public void cleanUp() { super.cleanUp(); LoggerContext loggerContext = getLoggerContext(); markAsUninitialized(loggerContext); loggerContext.getConfiguration().removeFilter(FILTER); } 调用父类的cleanUp方法,删除rootLogger配置的handler获得LoggerContext

设置ExternalContext 为null,代码如下:

private void markAsUninitialized(LoggerContext loggerContext) { loggerContext.setExternalContext(null); }删除FILTER

Log4J2LoggingSystem 集成

由于spring boot 默认依赖的logback,因此我们需要去除,修改pom文件如下:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency>

加入 Log4J2LoggingSystem的依赖.pom文件如下:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency>

Log4J2LoggingSystem生命周期

由于SpringBootConfigurationFactory继承了ConfigurationFactory,因此当使用Log4J2LoggingSystem 的时候,由于log还没有初始化,此时先使用DefaultConfiguration,后续会使用指定的配置.代码如下:

@Plugin(name = "SpringBootConfigurationFactory", category = ConfigurationFactory.CATEGORY) @Order(0) public class SpringBootConfigurationFactory extends ConfigurationFactory { private static final String[] TYPES = { ".springboot" }; @Override protected String[] getSupportedTypes() { return TYPES; } @Override public Configuration getConfiguration(LoggerContext loggerContext, ConfigurationSource source) { if (source != null && source != ConfigurationSource.NULL_SOURCE) { if (LoggingSystem.get(loggerContext.getClass().getClassLoader()) != null) { return new DefaultConfiguration(); } } return null; } }

当在类路径下存在.springboot结尾的文件,则会调用该类的getConfiguration方法.由于在spring-boot/src/main/resources/ 下,存在log4j2.springboot,因此该类会被执行.其文件内容如下:

See SpringBootConfigurationFactory

此时由于传入的ConfigurationSource不等于null,此时传入的是log4j2.springboot,因此会返回DefaultConfiguration

ApplicationStartingEvent事件处理–>执行Log4J2LoggingSystem#beforeInitialize方法.在该方法中会调用Slf4JLoggingSystem#configureJdkLoggingBridgeHandler,会判断org.slf4j.bridge.SLF4JBridgeHandler是否存在,此时由于加入了spring-boot-starter-log4j2,因此加入了jul-to-slf4 jar 包, 该类就是在该包中,因此会执行后续操作,代码如下:

private void configureJdkLoggingBridgeHandler() { try { // 1. 如果在当前类路径下存在org.slf4j.bridge.SLF4JBridgeHandler,则 if (isBridgeHandlerAvailable()) { // 1.1 删除slf4j 中root logger 配置的所有handler removeJdkLoggingBridgeHandler(); // 1.2 为root logger添加SLF4JBridgeHandler SLF4JBridgeHandler.install(); } } catch (Throwable ex) { // Ignore. No java.util.logging bridge is installed. } }

ApplicationEnvironmentPreparedEvent–> 最终会调用Log4J2LoggingSystem#initialize.

ApplicationPreparedEvent事件–> 向beanFactory进行注册.

ContextClosedEvent–>执行Log4J2LoggingSystem#cleanUp方法.

jvm退出前,执行Log4J2LoggingSystem注册的ShutdownHandler,将LoggerContext停止掉.代码如下:

public void run() { getLoggerContext().stop(); }
转载请注明原文地址: https://www.6miu.com/read-2150016.html

最新回复(0)