美文网首页
springboot日志系统

springboot日志系统

作者: apart_times | 来源:发表于2018-12-31 22:06 被阅读0次

    日志系统种类

    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<String, String> 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日志系统

          本文链接:https://www.haomeiwen.com/subject/abbwlqtx.html