• logback源码阅读(三) springboot对LoggingSystem日志系统的支持


    logback源码阅读(一)获取ILoggerFactory、LoggerContextInitializer.autoConfig()的findURLOfDefaultConfigurationFile方法中,我们知道默认配置配置文件是依次按照logback.configurationFile,logback-test.xml,logback.xml得到。但是很多项目中是这么配置的。

    logging:
      config: classpath:logback-ae.xml
    
    • 1
    • 2
    1. 这样配置的优势是什么?由于标准的logback.xml配置文件加载的太早,所以你不能在里面使用扩展部分。你需要使用logback-spring.xml或者通过logging.config自定义比如读取系统变量等
    2. 这样配置是怎么加载的配置文件?

    回答第2个问题之前,希望读者已经了解过springboot的启动流程
    springboot启动原理
    以及logback源码阅读(一)获取ILoggerFactory、Logger
    接下来,我会结合启动springboot的启动流程一探究竟,springboot是怎么支持日志系统的

    ConfigurableApplicationContext run(String… args)

    该方法是springboot的启动最外层方法

    public ConfigurableApplicationContext run(String... args) {
    		StopWatch stopWatch = new StopWatch();
    		stopWatch.start();
    		ConfigurableApplicationContext context = null;
    		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    		configureHeadlessProperty();
    		SpringApplicationRunListeners listeners = getRunListeners(args);
    		listeners.starting();
    		try {
    			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
    			configureIgnoreBeanInfo(environment);
    			Banner printedBanner = printBanner(environment);
    			context = createApplicationContext();
    			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
    					new Class[] { ConfigurableApplicationContext.class }, context);
    			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
    			refreshContext(context);
    			afterRefresh(context, applicationArguments);
    			stopWatch.stop();
    			if (this.logStartupInfo) {
    				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
    			}
    			listeners.started(context);
    			callRunners(context, applicationArguments);
    		}
    		catch (Throwable ex) {
    			handleRunFailure(context, ex, exceptionReporters, listeners);
    			throw new IllegalStateException(ex);
    		}
    
    		try {
    			listeners.running(context);
    		}
    		catch (Throwable ex) {
    			handleRunFailure(context, ex, exceptionReporters, null);
    			throw new IllegalStateException(ex);
    		}
    		return context;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    SpringApplicationRunListeners listeners = getRunListeners(args)

    这个方法会从META-INF/spring.factories 中读取Key为 org.springframework.boot.SpringApplicationRunListener 的Values,比如在spring-boot包中的定义的spring.factories:

    # Run Listeners
    org.springframework.boot.SpringApplicationRunListener=\
    org.springframework.boot.context.event.EventPublishingRunListener
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    public interface SpringApplicationRunListener {
        void starting();
    
        void environmentPrepared(ConfigurableEnvironment environment);
    
        void contextPrepared(ConfigurableApplicationContext context);
    
        void contextLoaded(ConfigurableApplicationContext context);
    
        void started(ConfigurableApplicationContext context);
    
        void running(ConfigurableApplicationContext context);
    
        void failed(ConfigurableApplicationContext context, Throwable exception);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    它主要是负责发布SpringApplicationEvent事件的,它会利用一个内部的ApplicationEventMulticaster在上下文实际被刷新之前对事件进行处理。

    LoggingApplicationListener

    1. LoggingApplicationListener是配置 LoggingSystem 的 ApplicationListener。
    2. 如果环境包含 logging.config 属性,它将用于引导日志系统,否则使用默认配置。
    3. 无论如何,如果环境包含 logging.level,则日志级别将被自定义。条目和日志记录组可以使用 logging.group 定义。
    4. 默认情况下,日志输出仅写入控制台。如果需要日志文件,可以使用 logging.path 和 logging.file 属性
    public void onApplicationEvent(ApplicationEvent event) {
    		if (event instanceof ApplicationStartingEvent) {
    			onApplicationStartingEvent((ApplicationStartingEvent) event);
    		}
    		else if (event instanceof ApplicationEnvironmentPreparedEvent) {
    			onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
    		}
    		else if (event instanceof ApplicationPreparedEvent) {
    			onApplicationPreparedEvent((ApplicationPreparedEvent) event);
    		}
    		else if (event instanceof ContextClosedEvent
    				&& ((ContextClosedEvent) event).getApplicationContext().getParent() == null) {
    			onContextClosedEvent();
    		}
    		else if (event instanceof ApplicationFailedEvent) {
    			onApplicationFailedEvent();
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    onApplicationEvent方法根据多播器派发的事件对loggingSystem做不同的处理

    onApplicationStartingEvent

    当sprignboot刚启动的时候listeners.starting();,会执行loggingSystem.beforeInitialize(),此时环境变量还没有被设置

    private void onApplicationStartingEvent(ApplicationStartingEvent event) {
    		this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
    		this.loggingSystem.beforeInitialize();
    	}
    
    • 1
    • 2
    • 3
    • 4

    LoggingSystem的获取就是从类路径加载一下类

    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);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    可以看到现在支持LogbackLoggingSystem,Log4J2LoggingSystem和JavaLoggingSystem,如果加载了多个就取第一个,默认LogbackLoggingSystem

    public static LoggingSystem get(ClassLoader classLoader) {
    		String loggingSystem = System.getProperty(SYSTEM_PROPERTY);
    		if (StringUtils.hasLength(loggingSystem)) {
    			if (NONE.equals(loggingSystem)) {
    				return new NoOpLoggingSystem();
    			}
    			return get(classLoader, loggingSystem);
    		}
    		return SYSTEMS.entrySet().stream().filter((entry) -> ClassUtils.isPresent(entry.getKey(), classLoader))
    				.map((entry) -> get(classLoader, entry.getValue())).findFirst()
    				.orElseThrow(() -> new IllegalStateException("No suitable logging system located"));
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    然后调用beforeInitialize()方法

    public void beforeInitialize() {
    		LoggerContext loggerContext = getLoggerContext();
    		if (isAlreadyInitialized(loggerContext)) {
    			return;
    		}
    		super.beforeInitialize();
    		loggerContext.getTurboFilterList().add(FILTER);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    如果loggerContext没有被初始化过,则调用父类beforeInitialize()删除Jdk Logging Bridge 处理程序,需要关注的是,最后加了一个过滤器阻止日志的打印也就是说一直到springboot启动的下一个阶段都不会打印日志

    private static final TurboFilter FILTER = new TurboFilter() {
    
    		@Override
    		public FilterReply decide(Marker marker, ch.qos.logback.classic.Logger logger, Level level, String format,
    				Object[] params, Throwable t) {
    			return FilterReply.DENY;
    		}
    
    	};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    ApplicationEnvironmentPreparedEvent

    这个时间发生在springboot启动的prepareEnvironment过程,这个阶段配置文件全部都加载完成,环境变量也都构建完毕。此时可以初始化日志系统(LoggingSystem)了

    private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
    		if (this.loggingSystem == null) {
    			this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
    		}
    		initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    先判断loggingSystem是否存在,不存在则获取,最后调用initialize方法

    protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) {
    		new LoggingSystemProperties(environment).apply();
    		this.logFile = LogFile.get(environment);
    		if (this.logFile != null) {
    			this.logFile.applyToSystemProperties();
    		}
    		initializeEarlyLoggingLevel(environment);
    		initializeSystem(environment, this.loggingSystem, this.logFile);
    		initializeFinalLoggingLevels(environment, this.loggingSystem);
    		registerShutdownHookIfNecessary(environment, this.loggingSystem);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    new LoggingSystemProperties(environment).apply();apply方法定义了系统变量与日志系统间参数的映射关系

    ```java
    public void apply() {
    		apply(null);
    	}
    
    	public void apply(LogFile logFile) {
    		PropertyResolver resolver = getPropertyResolver();
    		setSystemProperty(resolver, EXCEPTION_CONVERSION_WORD, "exception-conversion-word");
    		setSystemProperty(PID_KEY, new ApplicationPid().toString());
    		setSystemProperty(resolver, CONSOLE_LOG_PATTERN, "pattern.console");
    		setSystemProperty(resolver, FILE_LOG_PATTERN, "pattern.file");
    		setSystemProperty(resolver, FILE_MAX_HISTORY, "file.max-history");
    		setSystemProperty(resolver, FILE_MAX_SIZE, "file.max-size");
    		setSystemProperty(resolver, LOG_LEVEL_PATTERN, "pattern.level");
    		setSystemProperty(resolver, LOG_DATEFORMAT_PATTERN, "pattern.dateformat");
    		if (logFile != null) {
    			logFile.applyToSystemProperties();
    		}
    	}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    this.logFile = LogFile.get(environment);这个方法从logging.file和logging.path来获取日志输出的文件或者位置

    initializeEarlyLoggingLevel方法用来初始化springboot早期的日志级别

    private void initializeEarlyLoggingLevel(ConfigurableEnvironment environment) {
    		if (this.parseArgs && this.springBootLogging == null) {
    			if (isSet(environment, "debug")) {
    				this.springBootLogging = LogLevel.DEBUG;
    			}
    			if (isSet(environment, "trace")) {
    				this.springBootLogging = LogLevel.TRACE;
    			}
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    initializeSystem(environment, this.loggingSystem, this.logFile);初始化日志系统

    private void initializeSystem(ConfigurableEnvironment environment, LoggingSystem system, LogFile logFile) {
    		LoggingInitializationContext initializationContext = new LoggingInitializationContext(environment);
    		//读取logging.config
    		String logConfig = environment.getProperty(CONFIG_PROPERTY);
    		if (ignoreLogConfig(logConfig)) {//logging.config配置的-D开头则忽略该配置
    			system.initialize(initializationContext, null, logFile);
    		}
    		else {
    			try {
    				ResourceUtils.getURL(logConfig).openStream().close();
    				//初始化
    				system.initialize(initializationContext, logConfig, logFile);
    			}
    			catch (Exception ex) {
    				// NOTE: We can't use the logger here to report the problem
    				System.err.println(
    						"Logging system failed to initialize " + "using configuration from '" + logConfig + "'");
    				ex.printStackTrace(System.err);
    				throw new IllegalStateException(ex);
    			}
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    initialize则是根据configLocation和logFile进行初始化

    public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
    		if (StringUtils.hasLength(configLocation)) {
    			initializeWithSpecificConfig(initializationContext, configLocation, logFile);
    			return;
    		}
    		initializeWithConventions(initializationContext, logFile);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    最后loggerContext.getTurboFilterList().remove(FILTER);去掉之前加入的过滤器,日志系统开始能打印日志markAsInitialized(loggerContext)将loggerContext标记为已经初始化

    initializeFinalLoggingLevels(environment, this.loggingSystem);方法读取logger.level前缀的配置来调整最终的日志级别

    private void initializeFinalLoggingLevels(ConfigurableEnvironment environment, LoggingSystem system) {
    		if (this.springBootLogging != null) {
    			initializeLogLevel(system, this.springBootLogging);
    		}
    		setLogLevels(system, environment);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如果springBootLogging不为空,先设置日志级别为springBootLogging

    setLogLevels获取logger.level前缀的配置来调整日志级别,配置文件可以是apollo,也可以是application.yml或者下图的任意配置
    在这里插入图片描述

    protected void setLogLevels(LoggingSystem system, Environment environment) {
    		if (!(environment instanceof ConfigurableEnvironment)) {
    			return;
    		}
    		Binder binder = Binder.get(environment);
    		Map<String, String[]> groups = getGroups();
    		binder.bind(LOGGING_GROUP, STRING_STRINGS_MAP.withExistingValue(groups));
    		Map<String, String> levels = binder.bind(LOGGING_LEVEL, STRING_STRING_MAP).orElseGet(Collections::emptyMap);
    		levels.forEach((name, level) -> {
    			String[] groupedNames = groups.get(name);
    			if (ObjectUtils.isEmpty(groupedNames)) {
    				setLogLevel(system, name, level);
    			}
    			else {
    				setLogLevel(system, groupedNames, level);
    			}
    		});
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    setLogLevel方法可以结合之前的文章读者自行阅读

    ApplicationPreparedEvent

    这个事件发生在springboot启动的prepareContext阶段prepareContext(context, environment, listeners, applicationArguments, printedBanner);
    此刻容器上下文已经准备好

    private void onApplicationPreparedEvent(ApplicationPreparedEvent event) {
    		ConfigurableListableBeanFactory beanFactory = event.getApplicationContext().getBeanFactory();
    		if (!beanFactory.containsBean(LOGGING_SYSTEM_BEAN_NAME)) {
    			beanFactory.registerSingleton(LOGGING_SYSTEM_BEAN_NAME, this.loggingSystem);
    		}
    		if (this.logFile != null && !beanFactory.containsBean(LOGFILE_BEAN_NAME)) {
    			beanFactory.registerSingleton(LOGFILE_BEAN_NAME, this.logFile);
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这个方法就是把loggingSystem和logFile注册进容器

    ContextClosedEvent || ApplicationFailedEvent

    这两个阶段分别是容器关闭和SpringBoot启动失败,都需要做一些清理动作

    private void onContextClosedEvent() {
    		if (this.loggingSystem != null) {
    			this.loggingSystem.cleanUp();
    		}
    	}
    
    	private void onApplicationFailedEvent() {
    		if (this.loggingSystem != null) {
    			this.loggingSystem.cleanUp();
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    都是调用cleanUp方法

    public void cleanUp() {
    		LoggerContext context = getLoggerContext();
    		markAsUninitialized(context);
    		super.cleanUp();
    		context.getStatusManager().clear();
    		context.getTurboFilterList().remove(FILTER);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. loggerContext标记为没有初始化
    2. 删除 Jdk Logging Bridge 处理程序
    3. 清除状态管理器
    4. 删除早期加入的过滤器

    遗留问题

    为什么日志系统初始化了两次?bootstrapContext

  • 相关阅读:
    使用Hbuilder将vue项目封装为移动端安装包(apk)
    12345
    电脑硬盘分区软件哪个好用,无损分区软件哪个好
    网络面试-ox09 http是如何维持用户的状态?
    2023华为杯数学建模D题——碳排放路径优化基于指数分解法的LMDI 模型
    虚幻引擎 快速的色度抠图 Chroma Key 算法
    .netCore .net5,6,7 存日志文件
    每日一练 | 华为认证真题练习Day118
    【前端系列】vue
    Azure Terraform(十三)提升 Azure Web App Plan 的性能
  • 原文地址:https://blog.csdn.net/Ethan_199402/article/details/126055101