springboot日志系统

日志系统种类

springboot通过slf4j门面支持多种日志系统:主要包含

  • JavaLoggingSystem
  • LogbackLoggingSystem
  • Log4j2LoggingSystem
    详情参考下图:


    LoggingSystem.JPG

日志系统初始化

spring.factories

日志系统的初始化主要是通过LoggingApplicationListener类来实现,详细代码见springboot依赖中META-INF下的spring.factories文件

# Application Listeners
org.springframework.context.ApplicationListener=\
...
org.springframework.boot.context.logging.LoggingApplicationListener,\
...

LoggingApplicationListener初始化日志系统

其中有若干方法,完成了

  • 监听Event事件, 注册日志系统bean,初始化日志系统
  • 日志系统初始化(initialize, initializeSystem),
  • 初始化日志级别(initializeLogLevel, setLogLevels)

核心获取日志系统的代码如下:

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
    if (this.loggingSystem == null) {
        // 通过LoggingSystem获取对应的LoggingSystem(LogBackLoggingSystem/Log4j2LoggingSystem/JavaLoggingSystem/NoOpLoggingSystem)
        this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
    }

    this.initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
}

LoggingSystem获取日志系统

如果通过系统参数指定了日志系统,那么使用指定的日志系统;否则遍历静态常量SYSTEMS中的值,获取已存在的日志类对应的日志系统的第一个

public static LoggingSystem get(ClassLoader classLoader) {
    // SYSTEM_PROPERTY = "LoggingSystem"
    String loggingSystem = System.getProperty(SYSTEM_PROPERTY);
    if (StringUtils.hasLength(loggingSystem)) {
        // 如果指定了LoggingSystem,就用已有配置,可以用来自定义日志系统
        return (LoggingSystem)("none".equals(loggingSystem) ? new LoggingSystem.NoOpLoggingSystem() : get(classLoader, loggingSystem));
    } else {
        // 否则遍历SYSTEMS中已存在的日志系统的第一个
        return (LoggingSystem)SYSTEMS.entrySet().stream().filter((entry) -> {
            // 查看日志系统的类是否存在
            return ClassUtils.isPresent((String)entry.getKey(), classLoader);
        }).map((entry) -> {
            // 获取日志系统
            return get(classLoader, (String)entry.getValue());
            // 取得第一个
        }).findFirst().orElseThrow(() -> {
            return new IllegalStateException("No suitable logging system located");
        });
    }
}

SYSTEMS的值如下所示:

static {
    Map systems = new LinkedHashMap();
    systems.put("ch.qos.logback.core.Appender", "org.springframework.boot.logging.logback.LogbackLoggingSystem");
    systems.put("org.apache.logging.log4j.core.impl.Log4jContextFactory", "org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");
    systems.put("java.util.logging.LogManager", "org.springframework.boot.logging.java.JavaLoggingSystem");
    SYSTEMS = Collections.unmodifiableMap(systems);
}

从图中可以看出遍历会首先寻找LogbackLoggingSystem,然后是Log4j2LoggingSystem,最后是JavaLoggingSystem,如果系统有某个日志系统相关类,那么这个日志系统就会被初始化为应用的日志系统,下面具体的初始化工作交给具体日志系统

具体的日志系统初始化(Log4j2LoggingSystem为例)

Log4J2LoggingSystem继承自Slf4JLoggingSystem, Slf4JLoggingSystem继承自AbstractLoggingSystem,AbstractLoggingSystem继承LoggingSystem,从父类往子类看

  • LoggingSystem是一个抽象类,主要定义了initialize(),getLoggerConfigurations(),setLogLevel()等方法让子类实现
  • AbstractLoggingSystem抽象类,提供初始化initialize()实现(配置文件,默认),findConfig()寻找配置文件,和新的抽象方法loadDefaults(),loadConfiguration(),这两个方法交于子类实现加载日志配置
  • Slf4JLoggingSystem抽象类,configureJdkLoggingBridgeHandler()主要配置SLF4JBridgeHandler相关(这块不是很清楚)
  • Log4J2LoggingSystem核心类,完成loadDefaults(),loadConfiguration()日志配置,设置日志级别等工作

下面详细解释Log4J2LoggingSystem代码:

日志系统初始化

  • beforeInitialize()主要实现Slf4JLoggingSystem的SLF4JBridgeHandler初始化
  • initialize()主要从AbstractLoggingSystem查找初始化的方式(配置文件(包含xml文件和自定义配置),还是默认方式)
  • reinitialize()通过LoggerContext重新加载配置URL
// 初始化前的准备工作,调用父类beforeInitialize方法,调用到AbstractLoggingSystem结束
// AbstractLoggingSystem中是个空方法
public void beforeInitialize() {
    LoggerContext loggerContext = this.getLoggerContext();
    if (!this.isAlreadyInitialized(loggerContext)) {
        super.beforeInitialize();
        loggerContext.getConfiguration().addFilter(FILTER);
    }
 }
// 初始化调用父类的initialize方法,进入AbstractLoggingSystem的initialize方法
// 这里FILTER是AbstractFilter,不理解是什么作用
/** 
* Log4J2LoggingSystem代码片段
*/
public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile){
    LoggerContext loggerContext = this.getLoggerContext();
    if (!this.isAlreadyInitialized(loggerContext)) {
        loggerContext.getConfiguration().removeFilter(FILTER);
        super.initialize(initializationContext, configLocation, logFile);
        this.markAsInitialized(loggerContext);
    }
}
/** 
* AbstractLoggingSystem代码片段
*/
public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile){
    // 有配置项,使用配置项
    if (StringUtils.hasLength(configLocation)) {
        this.initializeWithSpecificConfig(initializationContext, configLocation, logFile);
    } else {
        // 否则使用默认的
        this.initializeWithConventions(initializationContext, logFile);
    }
}
/** 
* Log4J2LoggingSystem代码片段
* 调用LoggerContext刷新配置
*/
protected void reinitialize(LoggingInitializationContext initializationContext) {
    this.getLoggerContext().reconfigure();
}

日志系统加载配置项

  • loadDefaults: logFile不为空,从org.springframework.boot.logging依赖的classpath下找log4j2-file.xml;否则从org.springframework.boot.logging依赖的classpath下找log4j2.xml,loadDefaults具体实现交给loadConfiguration
  • loadConfiguration: 初始化LoggingSystem一些列日志重要参数,调用自己loadConfiguration初始化配置
// loadDefaults加载日志核心配置,具体实现交给loadConfiguration
protected void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile) {
    if (logFile != null) {
        this.loadConfiguration(this.getPackagedConfigFile("log4j2-file.xml"), logFile);
    } else {
        this.loadConfiguration(this.getPackagedConfigFile("log4j2.xml"), logFile);
    }
}
// 下面两个loadConfiguration都是核心配置
/**
* Log4J2LoggingSystem代码片段
*/
protected void loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile) {
    // 设置LoggingSystem核心参数
    super.loadConfiguration(initializationContext, location, logFile);
    // 设置Log4j2核心参数
    this.loadConfiguration(location, logFile);
}

/**
* LoggingSystem代码片段(核心参数)
*/
public void apply(LogFile logFile) {
    PropertyResolver resolver = this.getPropertyResolver();
    this.setSystemProperty(resolver, "LOG_EXCEPTION_CONVERSION_WORD", "exception-conversion-word");
    this.setSystemProperty("PID", (new ApplicationPid()).toString());
    this.setSystemProperty(resolver, "CONSOLE_LOG_PATTERN", "pattern.console");
    this.setSystemProperty(resolver, "FILE_LOG_PATTERN", "pattern.file");
    this.setSystemProperty(resolver, "LOG_FILE_MAX_HISTORY", "file.max-history");
    this.setSystemProperty(resolver, "LOG_FILE_MAX_SIZE", "file.max-size");
    this.setSystemProperty(resolver, "LOG_LEVEL_PATTERN", "pattern.level");
    this.setSystemProperty(resolver, "LOG_DATEFORMAT_PATTERN", "pattern.dateformat");
    if (logFile != null) {
        logFile.applyToSystemProperties();
    }
}

/**
* Log4J2LoggingSystem代码片段(核心参数)
*/
protected void loadConfiguration(String location, LogFile logFile) {
    Assert.notNull(location, "Location must not be null");
    try {
        // 获取日志上下文
        LoggerContext ctx = this.getLoggerContext();
        // 初始化配置URL路径
        URL url = ResourceUtils.getURL(location);
        // 构造ConfigurationSource 对象
        ConfigurationSource source = this.getConfigurationSource(url);
        // 核心!!!!!!!!!!!!,日志上下文加锁,设置日志配置项
        ctx.start(ConfigurationFactory.getInstance().getConfiguration(ctx, source));
    } catch (Exception var6) {
        throw new IllegalStateException("Could not initialize Log4J2 logging from " + location, var6);
    }
}

后话

至此一个以Log4j2为日志系统的LoggingSystem核心配置初始化完毕,当然由于理解了源码,就会有其他的玩法:通过springboot配置中心动态修改日志系统的配置

核心就是:自定义一个日志系统(比如继承自Log4j2LoggingSystem):重写loadDefaults和loadConfiguration方法,使得日志配置项(JSON或者XML)可以从配置中心动态获取,生产出现bug如果日志不能定位出问题,可以临时修改日志配置项,定位问题。

你可能感兴趣的:(springboot日志系统)