8.spring对logback的支持

news/2025/2/25 17:09:14

文章目录

  • 一、入口
  • 二、源码解析
    • LoggingApplicationListener
  • 三、其它支持
  • 四、总结

本节以logback为背景介绍的

一、入口

gav: org.springframework.boot:spring-boot:3.3.4

spring.factories文件中有如下两个配置

org.springframework.boot.logging.LoggingSystemFactory=\
org.springframework.boot.logging.logback.LogbackLoggingSystem.Factory,\
org.springframework.boot.logging.log4j2.Log4J2LoggingSystem.Factory,\
org.springframework.boot.logging.java.JavaLoggingSystem.Factory

org.springframework.context.ApplicationListener=\
org.springframework.boot.context.logging.LoggingApplicationListener,\
// 省略其它的...

这里定义了一个ApplicationListener的监听器, 以及三种不同日志实现的工厂

说明: 本节只分析使用logback作为slf4j实现的场景

二、源码解析

LoggingApplicationListener

继承链: GenericApplicationListener -> GenericApplicationListener -> SmartApplicationListener -> ApplicationListener

监听的事件为ApplicationEvent

public class LoggingApplicationListener implements GenericApplicationListener {
    // 触发事件
    public void onApplicationEvent(ApplicationEvent event) {
        // 启动初期触发
		if (event instanceof ApplicationStartingEvent startingEvent) {
			onApplicationStartingEvent(startingEvent);
		}
        // 环境准备之后触发
		else if (event instanceof ApplicationEnvironmentPreparedEvent environmentPreparedEvent) {
			onApplicationEnvironmentPreparedEvent(environmentPreparedEvent);
		}
        // 容器启动完成触发
		else if (event instanceof ApplicationPreparedEvent preparedEvent) {
			onApplicationPreparedEvent(preparedEvent);
		}
        // 容器关闭触发
		else if (event instanceof ContextClosedEvent contextClosedEvent) {
			onContextClosedEvent(contextClosedEvent);
		}
        // 容器启动失败触发
		else if (event instanceof ApplicationFailedEvent) {
			onApplicationFailedEvent();
		}
	}
}

容器启动事件

private void onApplicationStartingEvent(ApplicationStartingEvent event) {
    // 实例化LoggingSystem对象
    this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
    // 初始化前置处理
    this.loggingSystem.beforeInitialize();
}

// LoggingSystem.get
public static LoggingSystem get(ClassLoader classLoader) {
    // 系统配置的LoggingSystem; key:org.springframework.boot.logging.LoggingSystem
    String loggingSystemClassName = System.getProperty(SYSTEM_PROPERTY);
    if (StringUtils.hasLength(loggingSystemClassName)) {
        if (NONE.equals(loggingSystemClassName)) {
            return new NoOpLoggingSystem();
        }
        return get(classLoader, loggingSystemClassName);
    }
    // SPI获取LoggingSystem, 顺序是LogbackLoggingSystem->Log4J2LoggingSystem->JavaLoggingSystem
    LoggingSystem loggingSystem = SYSTEM_FACTORY.getLoggingSystem(classLoader);
    Assert.state(loggingSystem != null, "No suitable logging system located");
    return loggingSystem;
}

// LogbackLoggingSystem#beforeInitialize
@Override
public void beforeInitialize() {
    // 获取logContext日志上下文
    LoggerContext loggerContext = getLoggerContext();
    if (isAlreadyInitialized(loggerContext)) {
        return;
    }
    super.beforeInitialize();
    configureJdkLoggingBridgeHandler();
    loggerContext.getTurboFilterList().add(FILTER);
}
// 获取logContext日志上下文
private LoggerContext getLoggerContext() {
    ILoggerFactory factory = getLoggerFactory();
    // ....
    return (LoggerContext) factory;
}
// 获取logContext日志上下文
private ILoggerFactory getLoggerFactory() {
    // slf4j获取LoggerContext
    ILoggerFactory factory = LoggerFactory.getILoggerFactory();
    while (factory instanceof SubstituteLoggerFactory) {
        try {
            Thread.sleep(50);
        }
        catch (InterruptedException ex) {
            // 设置当前线程的中断标志位,表示该线程已被请求中断,但并不会立即停止线程的执行。
            Thread.currentThread().interrupt();
            throw new IllegalStateException("Interrupted while waiting for non-substitute logger factory", ex);
        }
        factory = LoggerFactory.getILoggerFactory();
    }
    return factory;
}

方法小结

  1. 容器在启动时通过ApplicationStartingEvent事件创建日志上下文
  2. 可以通过系统属性配置LoggingSystem对象, key为org.springframework.boot.logging.LoggingSystem
  3. 如果没有指定使用的LoggingSystem, 那么通过SPI获取, 由于在spring.factories中配置的LoggingSystemFactory里面LogbackLoggingSystem.Factory在第一个, 所以默认使用的LogbackLoggingSystem.Factory(如果有logback相关包的话)
  4. 执行LogbackLoggingSystem的beforeInitialize进行前置初始化
  5. beforeInitialize中使用SLF4J创建日志上下文; 这里就是SL4FJ和logback的内容了, 通过前面文章的介绍, 大家应该很熟悉了

在容器启动时创建了LoggingSystem, 一般是LogbackLoggingSystem, 同时创建了日志上下文LogContext

环境准备事件

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
    SpringApplication springApplication = event.getSpringApplication();
    // 容器启动事件中创建过了, 一般是LogbackLoggingSystem
    if (this.loggingSystem == null) {
        this.loggingSystem = LoggingSystem.get(springApplication.getClassLoader());
    }
    // 进行初始化
    initialize(event.getEnvironment(), springApplication.getClassLoader());
}

protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) {
    // 这里创建LogbackLoggingSystemProperties, 用于给日志上下文添加必要的属性
    getLoggingSystemProperties(environment).apply();
    // 从环境变量中获取logging.file.name和logging.file.path, 然后构建LogFile
    this.logFile = LogFile.get(environment);
    if (this.logFile != null) {
        // 将logging.file.path的值添加到系统属性中, key为LOG_PATH
        // 将logging.file.path目录下spring.log文件的路径添加到系统属性中, key为LOG_FILE
        this.logFile.applyToSystemProperties();
    }
    this.loggerGroups = new LoggerGroups(DEFAULT_GROUP_LOGGERS);
    // 设置spring启动时的日志等级, 如果环境变量中有debug, 那么是debug登记, 
    // 如果环境变量中有trace, 那么是trace等级
    initializeEarlyLoggingLevel(environment);
    // 初始化LogbackLoggingSystem
    initializeSystem(environment, this.loggingSystem, this.logFile);
    // 环境变量中获取logging.group的内容添加到loggerGroups中, 并设置spring启动时相关包的日志等级
    initializeFinalLoggingLevels(environment, this.loggingSystem);
    // 添加shutdown的回调
    registerShutdownHookIfNecessary(environment, this.loggingSystem);
}

private void initializeSystem(ConfigurableEnvironment environment, LoggingSystem system, LogFile logFile) {
    // 环境变量中的logging.config, 指定的日志配置文件路径
    String logConfig = environment.getProperty(CONFIG_PROPERTY);
    if (StringUtils.hasLength(logConfig)) {
        // 去掉字符串两端的空格
        logConfig = logConfig.strip();
    }
    try {
        // 就封装了一个environment的getter方法
        LoggingInitializationContext initializationContext = new LoggingInitializationContext(environment);
        // 没有配置logging.config或者以-D开头
        if (ignoreLogConfig(logConfig)) {
            // LogbackLoggingSystem初始化
            system.initialize(initializationContext, null, logFile);
        }
        else {
            // LogbackLoggingSystem初始化
            system.initialize(initializationContext, logConfig, logFile);
        }
    }
    catch (Throwable ex) {
        // ...
    }
}

小结

  1. springboot在环境准备完成后发出ApplicationEnvironmentPreparedEvent事件, 然后开始对LogbackLoggingSystemProperties进行初始化
  2. 创建LogbackLoggingSystemProperties对象, 并添加系统变量值, 下面是添加的内容
  • LOGGED_APPLICATION_NAME:spring.application.name的值
  • PID: pid的值
  • CONSOLE_LOG_CHARSET: 环境变量中logging.charset.console的值, 默认是U8
  • FILE_LOG_CHARSET: 环境变量中logging.charset.file的值, 默认是U8
  • CONSOLE_LOG_THRESHOLD: 环境变量中logging.threshold.console的值, 可选true/false
  • LOG_EXCEPTION_CONVERSION_WORD: 环境变量中logging.exception-conversion-word的值
  • CONSOLE_LOG_PATTERN: 环境变量中logging.pattern.console的值
  • FILE_LOG_PATTERN: 环境变量中logging.pattern.file的值
  • LOG_LEVEL_PATTERN:环境变量中logging.pattern.level的值
  • LOG_DATEFORMAT_PATTERN: 环境变量中logging.pattern.dateformat的值
  • LOG_CORRELATION_PATTERN: 环境变量中logging.pattern.correlation的值
  • 如果环境变量中logging.file.name存在, 添加LOG_FILE: file的值 到系统变量中
  • 如果环境变量中logging.file.path存在, 添加LOG_PATH: path的值 到系统变量中
  1. 设置springboot的日志等级, 如果系统变量中有debug值, 设置为debug等级, 如果有trace值, 设置为trace等级
  2. 可以在系统变量中使用logging.config指定日志文件路径, 也可以不指定使用默认的logback.xml, 然后进行LogbackLoggingSystem的初始化
  3. 设置一些包/类的日志等级, 该等级由第3步即系统变量中有debug值或者trace值来设置, 可以配置的内置模块日志等级的有如下几个
  • 如果环境变量中有debug, 那么设置包sql相关的包org.springframework.jdbc.core, org.hibernate.SQL,org.jooq.tools.LoggerListener和web相关的包org.springframework.core.codec, org.springframework.http,org.springframework.web,org.springframework.boot.actuate.endpoint.web,org.springframework.boot.web.servlet.ServletContextInitializerBeans的日志级别为debug
  • 如果环境变量中有trace, 那么设置包org.springframeworkorg.apache.tomcat,org.apache.catalina,org.eclipse.jetty,org.hibernate.tool.hbm2ddl的日志级别为trace
  • 如果环境变量中有logging.level, 那么设置指定web或者sql的日志等级为配置的日志等级, logging.level可以这么配置
logging.level.web=info
logging.level.org.springframework.boot=info
## 等等与上面环境变量中可配置的包相同

当然处理默认的web和sql两种类型的包之外, 还可以使用环境变量logging.group来自定义springboot中包或者类的日志级别

这里环境变量中logging.level的优先级要高于debug的配置

LogbackLoggingSystem的初始化

public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
    // 容器启动事件中创建的日志上下文
    LoggerContext loggerContext = getLoggerContext();
    if (isAlreadyInitialized(loggerContext)) {
        return;
    }
    // 非aot环境下直接返回false, 那么这里就是true, 这里对aot环境下不考虑
    if (!initializeFromAotGeneratedArtifactsIfPossible(initializationContext, logFile)) {
        // 初始化的核心
        super.initialize(initializationContext, configLocation, logFile);
    }
    // 环境上下文添加到日志上下文中
    loggerContext.putObject(Environment.class.getName(), initializationContext.getEnvironment());
    loggerContext.getTurboFilterList().remove(FILTER);
    // 标记为初始化完成
    markAsInitialized(loggerContext);
    if (StringUtils.hasText(System.getProperty(CONFIGURATION_FILE_PROPERTY))) {
        getLogger(LogbackLoggingSystem.class.getName()).warn("Ignoring '" + CONFIGURATION_FILE_PROPERTY
                + "' system property. Please use 'logging.config' instead.");
    }
}

如果没有开启aot, 那么这个方法没有什么内容, 直接看AbstractLoggingSystem#initialize方法即可

AbstractLoggingSystem

public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
    // 环境变量中没有使用logging.config指定日志文件路径的话走这里
    if (StringUtils.hasLength(configLocation)) {
        initializeWithSpecificConfig(initializationContext, configLocation, logFile);
        return;
    }
    // 指定日志文件路径的话走这里
    initializeWithConventions(initializationContext, logFile);
}

// 使用logging.config指定日志文件路径的场景
private void initializeWithSpecificConfig(LoggingInitializationContext initializationContext, String configLocation,
			LogFile logFile) {
    // 使用系统属性中的值替换configLocation中的占位符
    configLocation = SystemPropertyUtils.resolvePlaceholders(configLocation);
    loadConfiguration(initializationContext, configLocation, logFile);
}

// 没有指定日志文件路径的场景
private void initializeWithConventions(LoggingInitializationContext initializationContext, LogFile logFile) {
    // logback支持的文件名,只取一个, 顺序为:"logback-test.groovy", "logback-test.xml", "logback.groovy", "logback.xml" 
    String config = getSelfInitializationConfig();
    // 存在上面这几种文件的话
    if (config != null && logFile == null) {
        // 重置容器状态, 并调用loadConfiguration方法开始解析配置
        reinitialize(initializationContext);
        return;
    }
    // 项目中没有配置默认的四个文件
    if (config == null) {
        // 这里获取spring扩展的四个文件名, 顺序为: "logback-test-spring.groovy", "logback-test-spring.xml", "logback-spring.groovy", "logback-spring.xml" 
        config = getSpringInitializationConfig();
    }
    // 存在配置文件的话
    if (config != null) {
        // 解析配置文件
        loadConfiguration(initializationContext, config, logFile);
        return;
    }
    // 使用一套默认的配置, appender仅为ConsoleAppender, 这里不做解释
    loadDefaults(initializationContext, logFile);
}

方法小结

  1. 如果使用logging.config指定了日志文件的路径(路径支持使用占位符, 将从系统变量中获取变量值), 使用loadConfiguration方法进行日志文件解析
  2. 如果没有指定日志文件的路径, 那么先获取默认配置文件(“logback-test.groovy”, “logback-test.xml”, “logback.groovy”, “logback.xml” ), 如果没有默认的配置文件, 取带有spring后缀的日志文件(“logback-test-spring.groovy”, “logback-test-spring.xml”, “logback-spring.groovy”, “logback-spring.xml” )
  3. 如果配置文件存在, 使用loadConfiguration方法进行日志文件解析
  4. 如果没有配置文件, 那么使用logback默认的容器, 以及一个ConsoleAppender

解析配置

protected void loadConfiguration(LoggingInitializationContext initializationContext, String location,
			LogFile logFile) {
    // 日志上下文
    LoggerContext loggerContext = getLoggerContext();
    // 停止并重启; 如果你的springBoot启动类中有静态属性Logger使用LoggerFactory.getLogger获取的话,它会在spring启动之前执行, 这里就会存在一个loggerContext, 需要关闭
    stopAndReset(loggerContext);
    withLoggingSuppressed(() -> {
        // initializationContext对象仅仅是环境上下文的载体, 提供getEnvironment方法
        if (initializationContext != null) {
            // 创建LogbackLoggingSystemProperties对象, 并将一堆环境变量添加到系统变量中, 上面的环境准备事件中有介绍
            applySystemProperties(initializationContext.getEnvironment(), logFile);
        }
        try {
            // 配置的日志文件资源
            Resource resource = new ApplicationResourceLoader().getResource(location);
            // 解析日志配置文件
            configureByResourceUrl(initializationContext, loggerContext, resource.getURL());
        }
        catch (Exception ex) {
            throw new IllegalStateException("Could not initialize Logback logging from " + location, ex);
        }
        // 标识日志容器被启动
        loggerContext.start();
    });
    // 打印解析异常信息, 略过
    reportConfigurationErrorsIfNecessary(loggerContext);
}

private void configureByResourceUrl(LoggingInitializationContext initializationContext, LoggerContext loggerContext,
			URL url) throws JoranException {
    // 只允许xml为后缀的文件
    if (url.getPath().endsWith(".xml")) {
        // 使用springboot视线的SpringBootJoranConfigurator来解析配置
        JoranConfigurator configurator = new SpringBootJoranConfigurator(initializationContext);
        configurator.setContext(loggerContext);
        // 开始解析配置
        configurator.doConfigure(url);
    }
    else {
        throw new IllegalArgumentException("Unsupported file extension in '" + url + "'. Only .xml is supported");
    }
}

方法小结

  1. 添加一些环境变量参数到系统变量中
  2. 配置文件仅支持xml结尾的文件, 然后使用SpringBootJoranConfigurator来解析日志配置文件, 这里是对JoranConfigurator的扩展

SpringBootJoranConfigurator

class SpringBootJoranConfigurator extends JoranConfigurator {

	private final LoggingInitializationContext initializationContext;

	SpringBootJoranConfigurator(LoggingInitializationContext initializationContext) {
		this.initializationContext = initializationContext;
	}

	@Override
	protected void addModelHandlerAssociations(DefaultProcessor defaultProcessor) {
        // 添加处理configuration/springProperty的handler
		defaultProcessor.addHandler(SpringPropertyModel.class,
				(handlerContext, handlerMic) -> new SpringPropertyModelHandler(this.context,
						this.initializationContext.getEnvironment()));
        // 添加处理*/springProfile的handler
		defaultProcessor.addHandler(SpringProfileModel.class,
				(handlerContext, handlerMic) -> new SpringProfileModelHandler(this.context,
						this.initializationContext.getEnvironment()));
		super.addModelHandlerAssociations(defaultProcessor);
	}

	@Override
	public void addElementSelectorAndActionAssociations(RuleStore ruleStore) {
		super.addElementSelectorAndActionAssociations(ruleStore);
        // 添加允许的标签configuration/springProperty
		ruleStore.addRule(new ElementSelector("configuration/springProperty"), SpringPropertyAction::new);
        // 添加允许的标签*/springProfile
		ruleStore.addRule(new ElementSelector("*/springProfile"), SpringProfileAction::new);
		ruleStore.addTransparentPathPart("springProfile");
	}

	@Override
	public void buildModelInterpretationContext() {
		super.buildModelInterpretationContext();
        // modelInterpretationContext中的JoranConfigurator替换成SpringBootJoranConfigurator
		this.modelInterpretationContext.setConfiguratorSupplier(() -> {
			SpringBootJoranConfigurator configurator = new SpringBootJoranConfigurator(this.initializationContext);
			configurator.setContext(this.context);
			return configurator;
		});
	}
    
    // 省略一些代码...
}

SpringBootJoranConfigurator类在不考虑aot的情况下, 添加了对configuration/springProperty*/springProfile标签的支持, 其中*/springProfile是一种后缀标签的形式, 也就是说它可以放在任意标签的后面; 下面看看这两个handler

SpringPropertyModelHandler

@Override
public void handle(ModelInterpretationContext intercon, Model model) throws ModelHandlerException {
    SpringPropertyModel propertyModel = (SpringPropertyModel) model;
    // 作用域, 支持LOCAL(model上下文), CONTEXT(日志上下文), SYSTEM(系统级别); 默认是LOCAL, 在解析配置文件时有效
    Scope scope = ActionUtil.stringToScope(propertyModel.getScope());
    // 默认值
    String defaultValue = propertyModel.getDefaultValue();
    // source就是属性的名称
    String source = propertyModel.getSource();
    // name和source都不能为空
    if (OptionHelper.isNullOrEmpty(propertyModel.getName()) || OptionHelper.isNullOrEmpty(source)) {
        addError("The \"name\" and \"source\" attributes of <springProperty> must be set");
    }
    // 将属性添加到指定的作用域中
    PropertyModelHandlerHelper.setProperty(intercon, propertyModel.getName(), getValue(source, defaultValue),
            scope);
}
// 从环境变量中获取source属性对应的值
private String getValue(String source, String defaultValue) {
    if (this.environment == null) {
        addWarn("No Spring Environment available to resolve " + source);
        return defaultValue;
    }
    return this.environment.getProperty(source, defaultValue);
}

方法小结

  1. configuration/springProperty标签支持name,source,scope, defaultValue四个属性
  • name: 标签名称
  • source: 属性名称; 从环境变量中获取值的那个key
  • scope: 属性存放的位置, LOCAL:logback配置文件解析期间, CONTEXT:日志上线文范文内, SYSTEM: 系统属性

总的来说就是: configuration/springProperty将从环境变量中获取的值添加到日志容器中, 供解析日志使用, 其中key为name属性的值, value为source属性在环境变量中对应的值

例如:

// application.properties
log.fileName=info.log

// logback.xml
<configuration>
	<springProperty name="fileName" source="log.fileName" scope="LOCAL" defaultValue="temp.log"/>
</configuration>

SpringProfileModelHandler

class SpringProfileModelHandler extends ModelHandlerBase {

	private final Environment environment;

	@Override
	public void handle(ModelInterpretationContext intercon, Model model) throws ModelHandlerException {
		SpringProfileModel profileModel = (SpringProfileModel) model;
        // 如果当前spring的环境(spring.profiles.active)不是springProfile指定下的, 那么被springProfile标签包裹的子标签将不会生效
		if (!acceptsProfiles(intercon, profileModel)) {
			model.deepMarkAsSkipped();
		}
	}

	private boolean acceptsProfiles(ModelInterpretationContext ic, SpringProfileModel model) {
		if (this.environment == null) {
			return false;
		}
        // name根据逗号分割
		String[] profileNames = StringUtils
			.trimArrayElements(StringUtils.commaDelimitedListToStringArray(model.getName()));
		if (profileNames.length == 0) {
			return false;
		}
		for (int i = 0; i < profileNames.length; i++) {
			try {
                // 从LOCAL、CONTEXT、SYSTEM范围内获取值替换占位符; 没有占位符的话用原值
				profileNames[i] = OptionHelper.substVars(profileNames[i], ic, this.context);
			}
			catch (ScanException ex) {
				throw new RuntimeException(ex);
			}
		}
        // 判断是不是环境变量中配置的spring.profiles.active的值
		return this.environment.acceptsProfiles(Profiles.of(profileNames));
	}
}

方法小结

springProfile标签可以放在任意子标签下, 其中name属性用来指定当前的环境, 它可以指定什么环境下使用什么样的配置, 如果当前环境与springProfile配置的不同, 那么springProfile的子标签将不会生效; 例如

<root level="info">
    <springProfile name="dev,test">
        <appender-ref ref="CONSOLE" />
    </springProfile>
    <springProfile name="prod">
        <appender-ref ref="ROLLER" />
    </springProfile>
</root>

这种配置下dev或者test环境 CONSOLE的appender将会生效, ROLLER的appender不会生效

三、其它支持

spring还提供了ColorConverter,ExtendedWhitespaceThrowableProxyConverter,WhitespaceThrowableProxyConverter转换器, 用来给控制台输出颜色日志的、异常等

四、总结

  1. spring自动装配了LoggingApplicationListener监听器, 监听ApplicationEvent事件, 在springboot启动周期中对日志做了一些扩展
  • springboot启动初期(ApplicationStartingEvent事件), 实例化了LogbackLoggingSystem对象
  • 在环境准备完成后(ApplicationEnvironmentPreparedEvent事件), 对logback容器做了初始化并启动
  1. springboot对日志slf4j的实现默认顺序为LogbackLoggingSystem->Log4J2LoggingSystem->JavaLoggingSystem, 确保其中有ch.qos.logback:logback-classic:版本号的包

  2. 关于环境变量中配置的logging.file.namelogging.file.path属性, 是用来给默认日志配置设置滚动文件的, 就像appender中的file属性一样, 但是如果你配置了日志文件(例如logback.xml), 它就没什么用了

  3. 可以在环境变量中配置 debug=true或者trace=true来设置springboot内置包的日志等级; 同样也可以在环境变量中设置指定包的日志级别, 就不限于debug或者trace了, 例如 logging.level.web=info; logging.level.org.springframework.boot=info 这种logging.level.web=info的方式优先级高于debug=true

  4. 可以使用环境变量logging.config配置日志文件的位置, 支持classpath的配置, 即放在项目的resources目录下即可;

  • 如过没有使用logging.config指定日志配置, 那么会默认读取"logback-test.groovy", "logback-test.xml", "logback.groovy", "logback.xml"中的一个
  • 如果这一步也没有指定, 那么读取springboot扩展的配置文件"logback-test-spring.groovy", "logback-test-spring.xml", "logback-spring.groovy", "logback-spring.xml"
  • 如果直接没有配置文件, 那么默认构建ConsoleAppender和root的logger对象, 如果配置了logging.file.namelogging.file.path属性, 那么就会多创建一个info级别的RollingFileAppender
  1. springboot使用SpringBootJoranConfigurator扩展了JoranConfigurator, 添加了如下的相关支持
  • configuration/springProperty标签, 用来从环境变量中获取属性, 作用到解析日志文件中
  • */springProfile标签, 该标签是后缀匹配型, 可以放在任意位置, 它用于指定哪些配置在不同的环境下生效
  1. springboot还提供了额外的转换器, 例如ColorConverter, 大家配置ConsoleAppender的时候可以借用它
  2. 另外, 在遇到Thread.sleep的时候, 可以用Thread.currentThread().interrupt();设置当前线程的中断标志位,表示该线程已被请求中断,但并不会立即停止线程的执行。也曾看到一些地方什么都不处理, 这种应该是标准做法, 在几个源码中见过了

个人公众号
在这里插入图片描述


http://www.niftyadmin.cn/n/5865748.html

相关文章

【Java 8】Lambda表达式介绍

目录 1、Lambda简介 2、语法介绍 3、Lambda表达式示例 3.1、无参数的 Lambda 表达式 3.2、单个参数的 Lambda 表达式 3.3、多个参数的 Lambda 表达式 3.4、带语句块的 Lambda 表达式 4、Lambda使用场景 4.1、替代匿名内部类 4.2、集合操作 4.3、排序 4.4、函数式接口…

Java使用EasyExcel实现异步导出

以下是使用 EasyExcel 工具类实现异步导出功能的 Demo&#xff0c;包括用户发起导出请求后&#xff0c;系统先返回响应&#xff0c;后台读取数据并上传至 COS&#xff0c;最后通知用户下载的完整流程。 实现步骤 用户发起导出请求 前端调用导出接口&#xff0c;后端立即返回响应…

【Linux】Ubuntu中,如何创建软件的快捷方式放到桌面上

本文主要介绍Ubuntu中&#xff0c;如何创建软件的快捷方式放到桌面上 首先进入到/usr/share/applications/路径下&#xff0c;找到自己想要的软件&#xff0c;这里以我的vim为例子 ricardoDESKTOP-8T8LHV5:/usr/share/applications$ ls byobu.desktop io.snapcraft.SessionA…

AI回答:Linux C/C++编程学习路线

Linux C/C编程学习路线需要结合Linux系统特性和C/C语言的特点&#xff0c;以下是一个系统化的学习路径&#xff0c;适合从初学者到进阶者&#xff1a; 第一阶段&#xff1a;Linux基础 Linux操作系统基础 学习Linux基本命令&#xff1a;ls、cd、mkdir、rm、grep、find等。 理解…

软件项目开发中,产品经理借助 AI 工具将产品设计转化为需求的方法

软件项目开发中&#xff0c;产品经理借助 AI 工具将产品设计转化为需求的方法 在软件项目开发流程里&#xff0c;产品经理承担着将产品设计转化为详细需求的关键任务。AI 工具的兴起为这一过程带来了全新的助力&#xff0c;下面结合实例来看看产品经理如何巧妙运用 AI 工具。 一…

ubuntu-24.04.1-desktop 中安装 QT6.7

ubuntu-24.04.1-desktop 中安装 QT6.7 1 环境准备1.1 安装 GCC 和必要的开发包:1.2 Xshell 连接 Ubuntu2 安装 Qt 和 Qt Creator:2.1 下载在线安装器2.2 在虚拟机中为文件添加可执行权限2.3 配置镜像地址运行安装器2.4 错误:libxcb-xinerama.so.0: cannot open shared objec…

YOLO11改进-模块-引入混合结构模块Mix Structure Block 提高多尺度、小目标

在图像去雾领域&#xff0c;传统的基于卷积神经网络&#xff08;CNN&#xff09;和 Transformer 的方法存在局限性。CNN 方法大多存在感受野不足的问题&#xff0c;限制了单个像素在神经网络中的参考范围&#xff0c;部分考虑大感受野的 CNN 方法又忽略了图像的多尺度特性&…

Unity制作游戏项目——Unity项目如何导出安装包(Inno Setup Compiler的使用)——附有Inno Setup Compiler软件安装包

1.将完成的Unity项目构建成适合Windows平台可执行文件。 步骤一&#xff1a;点击File--Build Settings&#xff08;CtrlshiftB&#xff09; 步骤二&#xff1a;进行基础配置&#xff0c;点击build&#xff0c;选择将要导出的文件夹 “Windows”&#xff1a;具体指定了目标平台…