SpringBoot中logback的初始化流程

【Logback】Logback 的配置文件 首先,DefaultJoranConfigurator 会尝试查找“logback.configurationFile” 系统属性上指定的文件。如果可以找到该文件,则会读取并解释该文件,然后进行配置。如果没有找到上述文件,DefaultJoranConfigurator 将尝试在类路径上查找配置文件 “logback-test.xml” 。如果可以找到该文件,则会读取并解释该文件,然后进行配置。 如果没有找到上述文件,配置器将继续尝试在类路径中查找配置文件 阅读详情

一. 前言

1. 相关日志文档

2. 说明

  • 本文基于使用logback-spring.xml配置文件进行源码解析
    • Spring项目可以使用的配置文件
      • logback-test.groovy
      • logback-test.xml
      • logback.groovy
      • logback.xml
      • logback-test-spring.groovy
      • logback-test-spring.xml
      • logback-spring.groovy
      • logback-spring.xml (本文基于该配置进行讲述)
  • 本文只讲核心代码, 不是主线的代码不展示出来
  • 本文的spring-boot版本为2.7.11-SNAPSHOT
  • 本文涉及到的logback-classic依赖版本为1.2.12
  • 如有疑问可留意或查看官方文档

3. 代码准备

3.1 添加依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.7.11-SNAPSHOT</version>
        </dependency>

依赖说明:

  1. spring-boot-starter-web会自动引入spring-boot 和 logback所需的依赖
  2. 依赖引用关系见下图 (不使用@slf4j注解的话请忽略lombok依赖)
    在这里插入图片描述

3.2 添加logback-spring.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!-- 配置详情: https://blog.youkuaiyun.com/weixin_41377777/article/details/120962037 -->
<!-- 拷贝即可使用: 额外需要关注的就三个点, 1.配置spring.application.name可指定文件前缀名 2.修改dev的springProfile中自定义logger的name指定当前项目 3.修改rollingPolicy的MaxHistory确定日志保留数 -->

<configuration>
	<property name="LOG_PATH" value="/apps/logs" />
	<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %level [%C{36}.%M:%line] tid=%X{traceId}, %msg%n" />
	<springProperty scope="context" name="APP_NAME" source="spring.application.name" defaultValue="defaultAppName"/>

	<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
		<encoder>
			<pattern>${LOG_PATTERN}</pattern>
		</encoder>
	</appender>

	<!--本地环境 -->
	<springProfile name="dev">
		<logger name="com.chenlongji" level="DEBUG" />
		<!--<logger name="org.mybatis" level="DEBUG" />-->

		<root level="INFO">
			<appender-ref ref="CONSOLE" />
		</root>
	</springProfile>

	<!--测试环境 -->
	<springProfile name="test">
		<!-- 备注: 若springProfile仅激活一个, 那appender的name重复也不影响, 因为没激活的profile里面的appender不会进行初始化 -->
		<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
			<!-- layout使用encoder配置应该也行, 如CONSOLE的配置 -->
			<layout class="ch.qos.logback.classic.PatternLayout">
				<pattern>${LOG_PATTERN}</pattern>
			</layout>
			<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
				<FileNamePattern>${LOG_PATH}/${APP_NAME}.%d{yyyy-MM-dd}.log</FileNamePattern>
				<MaxHistory>1</MaxHistory>
			</rollingPolicy>
		</appender>

		<root level="INFO">
			<appender-ref ref="FILE" />
		</root>
	</springProfile>

	<!--生产环境 -->
	<springProfile name="prd">
		<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
			<layout class="ch.qos.logback.classic.PatternLayout">
				<pattern>${LOG_PATTERN}</pattern>
			</layout>
			<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
				<FileNamePattern>${LOG_PATH}/${APP_NAME}.%d{yyyy-MM-dd}.log</FileNamePattern>
				<MaxHistory>7</MaxHistory>
			</rollingPolicy>
		</appender>

		<root level="INFO">
			<appender-ref ref="FILE" />
		</root>
	</springProfile>

</configuration>

说明:

  1. 日志输出格式中的%X为mdc日志链路追踪使用, 这里不展开讲
  2. 确保没有logback.xml等日志配置文件, 否则logback-spring.xml可能不生效
  3. 若想了解配置详情, 点击查看此文章
  4. 若想了解配置对应的源码信息, 请看logback源码浅析

3.3 添加main测试方法

@SpringBootApplication
public class LogbackStudyApplication {

    public static void main(String[] args) {
		// 初始化入口
        ConfigurableApplicationContext context = SpringApplication.run(LogbackStudyApplication.class, args);

        Logger logger = LoggerFactory.getLogger(Test1.class);
        logger.info("我是info级别的日志");

        context.close();
    }

}

4. 最终初始化使用的配置文件说明

  • logback默认的配置文件
    • logback-test.groovy
    • logback-test.xml
    • logback.groovy
    • logback.xml
  • springboot默认的logback配置文件
    • logback-test-spring.groovy
    • logback-test-spring.xml
    • logback-spring.groovy
    • logback-spring.xml
  • 最终初始化使用的配置文件说明 (可以使用-Dlogging.config={xxx}指定配置文件)
    • logging.config指定配置文件, 则使用该文件完成logger最终初始化
    • logging.config未指定配置文件且有logback默认的配置文件, 则使用logback默认的配置文件完成logger最终初始化
    • logging.config未指定配置文件且无logback任意的配置文件, 则使用DefaultLogbackConfiguration完成logger最终初始化
    • logging.config未指定配置文件且无logback默认的配置文件且有springboot默认的logback配置文件, 则使用springboot默认的logback配置文件完成logger最终初始化

二. 执行原生logback初始化

前置说明:
springboot项目中是先执行原生的logback初始化, 再执行springboot下logback初始化的

入口

@SpringBootApplication
public class LogbackStudyApplication {
    public static void main(String[] args) {
		// 入口
        ConfigurableApplicationContext context = SpringApplication.run(LogbackStudyApplication.class, args);
    }
}

JVM加载SpringApplication类时, 完成其静态属性logger初始化

public class SpringApplication {
	// 这里的LogFactory全限定名为org.apache.commons.logging.LogFactory, 是spring-jcl.jar下的类
	private static final Log logger = LogFactory.getLog(SpringApplication.class);
}

进入LogFactory的getLog(Class)方法

	public static Log getLog(Class<?> clazz) {
		return getLog(clazz.getName());
	}

进入LogFactory的getLog(String)方法

	public static Log getLog(String name) {
		return LogAdapter.createLog(name);
	}

进入LogAdapter的createLog(String)方法

	public static Log createLog(String name) {
		// logApi枚举值来源于LogAdapter初始化是根据 是否可以类加载到某些类来确定值的. 不展开
		// 添加spring-boot-starter-web依赖后, logApi = SLF4J_LAL
		switch (logApi) {
			case LOG4J:
				return Log4jAdapter.createLog(name);
			case SLF4J_LAL:
				// 创建log对象
				return Slf4jAdapter.createLocationAwareLog(name);
			case SLF4J:
				return Slf4jAdapter.createLog(name);
			default:
				return JavaUtilAdapter.createLog(name);
		}
	}

进入Slf4jAdapter的createLocationAwareLog(String)方法

		public static Log createLocationAwareLog(String name) {
			// 此次的LoggerFactory全限定类名为org.slf4j.LoggerFactory, 是slf4j-api下的类
			// 因为spring-boot-starter-web依赖导入了logback-classic包, 故此次会进入logback原生的初始化流程
			Logger logger = LoggerFactory.getLogger(name);
			return (logger instanceof LocationAwareLogger 
					// 属于logback类型的logger, 包装为Slf4jLocationAwareLog返回
					? new Slf4jLocationAwareLog((LocationAwareLogger) logger) 
					: new Slf4jLog<>(logger));
		}

说明:

  1. 项目添加的spring-boot-starter-web依赖导入了logback-classic包, 后续会进入logback原生的初始化流程
  2. 原生logback初始化会使用logback.xml等不带spring字眼的配置文件, 若无则使用默认配置BasicConfigurator
  3. logback原生的初始化流程入口org.slf4j.LoggerFactory.getLogger(name). 请看logback源码浅析

三. 执行SpringBoot下logback的再次初始化

前置说明:

  1. 完成原生logback初始化后进入SpringApplication的run方法
  2. 然后一直执行到LoggingApplicationListener的initialize(ConfigurableEnvironment, ClassLoader)方法, 我们从这里开始看源码了解springboot下logback又如何再次初始化
  3. springboot代码量较多, 后面注释仅为logback相关的标注

进入LoggingApplicationListener的initialize(ConfigurableEnvironment, ClassLoader)方法

	protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) {
		getLoggingSystemProperties(environment).apply();
		this.logFile = LogFile.get(environment);
		if (this.logFile != null) {
			this.logFile.applyToSystemProperties();
		}
		this.loggerGroups = new LoggerGroups(DEFAULT_GROUP_LOGGERS);
		initializeEarlyLoggingLevel(environment);
		// 初始化日志系统
		initializeSystem(environment, this.loggingSystem, this.logFile);
		initializeFinalLoggingLevels(environment, this.loggingSystem);
		registerShutdownHookIfNecessary(environment, this.loggingSystem);
	}

进入LoggingApplicationListener的initializeSystem(ConfigurableEnvironment, LoggingSystem, LogFile)方法

	private void initializeSystem(ConfigurableEnvironment environment, LoggingSystem system, LogFile logFile) {
		// 获取环境变量中的logging.config指定的配置文件路径
		// 注: logback中使用logback.configurationFile指定配置文件路径(springboot下该值最终是无效的)
		String logConfig = StringUtils.trimWhitespace(environment.getProperty(CONFIG_PROPERTY));
		try {
			LoggingInitializationContext initializationContext = new LoggingInitializationContext(environment);
			
			if (ignoreLogConfig(logConfig)) {
				// 核心代码: logging.config没有指定配置文件, 则后续再获取符合要求的配置文件
				system.initialize(initializationContext, null, logFile);
			}
			else {
				// 核心代码: logging.config指定了配置文件, 则使用指定的
				system.initialize(initializationContext, logConfig, logFile);
			}
		}
		catch (Exception ex) {
			//...
		}
	}

进入LogbackLoggingSystem的initialize(LoggingInitializationContext, String, LogFile)方法

	public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
		LoggerContext loggerContext = getLoggerContext();
		// 判断logger上下文中, 是否已经执行了springboot下的logback初始化. 这里是使用LoggingSystem的全限定类名占位标志的
		if (isAlreadyInitialized(loggerContext)) {
			return;
		}
		// 核心代码: 初始化日志系统
		super.initialize(initializationContext, configLocation, logFile);
		loggerContext.getTurboFilterList().remove(FILTER);
		// 完成springboot下的logback初始化后, 设置logger上下文springboot的logback已初始化标志(LoggingSystem的全限定类名占位)
		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.");
		}
	}

进入AbstractLoggingSystem的initialize(LoggingInitializationContext, String, LogFile)方法

	public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
		// logging.config指定了配置文件, 则执行initializeWithSpecificConfig方法. 两个方法都在loadConfiguration(LoggingInitializationContext,String,LogFile)中汇合
		if (StringUtils.hasLength(configLocation)) {
			initializeWithSpecificConfig(initializationContext, configLocation, logFile);
			return;
		}
		// 未指定配置文件, 则执行initializeWithConventions方法. 两个方法都在loadConfiguration(LoggingInitializationContext,String,LogFile)中汇合
		initializeWithConventions(initializationContext, logFile);
	}

说明:

  1. initializeWithConventions方法读取默认的配置文件后, 会和initializeWithSpecificConfig在loadConfiguration(LoggingInitializationContext,String,LogFile)中汇合
  2. 这里我们加上没有指定配置文件, 我们继续从initializeWithConventions方法看源码

进入AbstractLoggingSystem的initializeWithConventions(LoggingInitializationContext, 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) {
			// 使用原有的logback默认配置文件再次初始化(使用同一个配置文件再次执行了原生的logback初始化一次). 发生属性更改的属性会被重新初始化. 这里不展开
			reinitialize(initializationContext);
			return;
		}
		if (config == null) {
			// 获取springboot特有的logback配置文件
			config = getSpringInitializationConfig();
		}
		if (config != null) {
			// 核心代码: 再次执行初始化
			loadConfiguration(initializationContext, config, logFile);
			return;
		}
		// 整个系统都没找到一个适合的配置文件, 则使用springboot默认的DefaultLogbackConfiguration配置类完成初始化. 不展开
		loadDefaults(initializationContext, logFile);
	}

说明: springboot初始logback过程汇总
logback默认的配置文件[logback-test.groovy, logback-test.xml, logback.groovy, logback.xml]
springboot默认的logback配置文件[logback-test-spring.groovy, logback-test-spring.xml, logback-spring.groovy, logback-spring.xml]
初始化过程汇总

  1. 系统无logback相关的任意配置文件. 则先使用logback默认配置类BasicConfigurator初始化一次, 再使用springboot的默认配置类DefaultLogbackConfiguration初始化一次
  2. 系统指定了配置文件但无logback默认配置文件. 则先使用logback默认配置类BasicConfigurator初始化一次, 再使用指定的配置文件初始化一次
  3. 系统指定了配置文件但有logback默认配置文件. 则先使用logback默认配置文件初始化一次, 再使用指定的配置文件初始化一次
  4. 系统未指定配置文件且存在logback默认配置文件. 则使用logback默认配置文件初始化两次
  5. 系统未指定配置文件且不存在logback默认配置文件且存在springboot的logback配置文件. 则先使用logback默认配置类BasicConfigurator初始化一次, 再使用springboot的logback配置文件初始化一次

最终汇总

  1. logging.config指定配置文件, 则使用该文件完成logger最终初始化
  2. logging.config未指定配置文件且有logback默认的配置文件, 则使用logback默认的配置文件完成logger最终初始化
  3. logging.config未指定配置文件且无logback任意的配置文件, 则使用DefaultLogbackConfiguration完成logger最终初始化
  4. logging.config未指定配置文件且无logback默认的配置文件且有springboot默认的logback配置文件, 则使用springboot默认的logback配置文件完成logger最终初始化

进入AbstractLoggingSystem的getSpringInitializationConfig()方法

	protected String getSpringInitializationConfig() {
		// getSpringConfigLocations()找出springboot默认的logback配置文件列表
		// findConfig()找到系统中存在第一个 springboot默认的logback配置文件列表内 的配置文件. 该方法不展开
		return findConfig(getSpringConfigLocations());
	}

进入AbstractLoggingSystem的getSpringConfigLocations()方法

	protected String[] getSpringConfigLocations() {
		// 获取当前日志系统默认的配置文件. 当前为logback, 默认的配置文件有logback-test.groovy, logback-test.xml, logback.groovy, logback.xml
		String[] locations = getStandardConfigLocations();
		// 遍历拼接上"-spring.", springboot默认的logback配置文件列表如下
		// [logback-test-spring.groovy, logback-test-spring.xml, logback-spring.groovy, logback-spring.xml]
		for (int i = 0; i < locations.length; i++) {
			String extension = StringUtils.getFilenameExtension(locations[i]);
			locations[i] = locations[i].substring(0, locations[i].length() - extension.length() - 1) + "-spring."
					+ extension;
		}
		return locations;
	}

进入LogbackLoggingSystem的loadConfiguration(LoggingInitializationContext, String,LogFile)方法

	protected void loadConfiguration(LoggingInitializationContext initializationContext, String location,
			LogFile logFile) {
		super.loadConfiguration(initializationContext, location, logFile);
		LoggerContext loggerContext = getLoggerContext();
		stopAndReset(loggerContext);
		try {
			// 通过配置文件重新初始化
			configureByResourceUrl(initializationContext, loggerContext, ResourceUtils.getURL(location));
		}
		catch (Exception ex) {
			throw new IllegalStateException("Could not initialize Logback logging from " + location, ex);
		}
		// ...
	}

进入LogbackLoggingSystem的configureByResourceUrl(LoggingInitializationContext, LoggerContext,URL)方法

	private void configureByResourceUrl(LoggingInitializationContext initializationContext, LoggerContext loggerContext,
			URL url) throws JoranException {
		if (XML_ENABLED && url.toString().endsWith("xml")) {
			// 使用SpringBootJoranConfigurator完成初始化
			JoranConfigurator configurator = new SpringBootJoranConfigurator(initializationContext);
			configurator.setContext(loggerContext);
			configurator.doConfigure(url);
		}
		else {
			new ContextInitializer(loggerContext).configureByResource(url);
		}
	}

说明:

  1. 后面执行的就是SpringBootJoranConfigurator.doConfigure()方法, 与logback初始化流程几乎一致了.
  2. logback初始化流程请看logback源码浅析
  3. 下面的[SpringBootJoranConfigurator与JoranConfigurator初始化的差异] 继续将springboot中的一些和logback初始化不同的东西

四. SpringBootJoranConfigurator与JoranConfigurator初始化的差异

1. 继承关系

在这里插入图片描述

说明:
SpringBootJoranConfigurator为JoranConfigurator的子类

2. 了解SpringBootJoranConfigurator类

class SpringBootJoranConfigurator extends JoranConfigurator {
	// springboot的logger初始化上下文对象, 存储了environment环境信息
	private LoggingInitializationContext initializationContext;

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

	/**
	 * 添加实例规则
	 */
	@Override
	public void addInstanceRules(RuleStore rs) {
		super.addInstanceRules(rs);
		Environment environment = this.initializationContext.getEnvironment();
		// 添加解析<configuration>下<springProperty>的Action
		rs.addRule(new ElementSelector("configuration/springProperty"), new SpringPropertyAction(environment));
		// 添加解析* <springProfile>的Action
		rs.addRule(new ElementSelector("*/springProfile"), new SpringProfileAction(environment));
		// 添加* <springProfile>下的Action. 该Action为无操作Action, 由SpringProfileAction去处理<springProfile>下的标签内容
		rs.addRule(new ElementSelector("*/springProfile/*"), new NOPAction());
	}
}

3. SpringBoot特有的SpringPropertyAction

class SpringPropertyAction extends Action {
	private static final String SOURCE_ATTRIBUTE = "source";
	private static final String DEFAULT_VALUE_ATTRIBUTE = "defaultValue";
	private final Environment environment;
	SpringPropertyAction(Environment environment) {
		this.environment = environment;
	}

	@Override
	public void begin(InterpretationContext context, String elementName, Attributes attributes) throws ActionException {
		String name = attributes.getValue(NAME_ATTRIBUTE);
		String source = attributes.getValue(SOURCE_ATTRIBUTE);
		Scope scope = ActionUtil.stringToScope(attributes.getValue(SCOPE_ATTRIBUTE));
		String defaultValue = attributes.getValue(DEFAULT_VALUE_ATTRIBUTE);
		if (OptionHelper.isEmpty(name) || OptionHelper.isEmpty(source)) {
			addError("The \"name\" and \"source\" attributes of <springProperty> must be set");
		}
		// 读取<springProperty>的属性值, 缓存到指定的作用域中待用. 不展开
		ActionUtil.setProperty(context, name, getValue(source, defaultValue), scope);
	}

	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);
	}

	@Override
	public void end(InterpretationContext context, String name) throws ActionException {
	}
}

4. SpringBoot特有的SpringProfileAction

class SpringProfileAction1 extends Action implements InPlayListener {
	// 环境信息
	private final Environment environment;
	// 深度. 用于拦截嵌套的<springProfile>错误标签
	private int depth = 0;
	// 记录当前的<springProfile>是否满足运行的环境(即springProfile的name是否包含 项目的spring.profiles.active)
	private boolean acceptsProfile;
	// 事件暂存区. 寄存<springProfile>及内嵌标签事件的 缓存事件列表
	private List<SaxEvent> events;

	SpringProfileAction1(Environment environment) {
		this.environment = environment;
	}

	@Override
	public void begin(InterpretationContext ic, String name, Attributes attributes) throws ActionException {
		// 深度加一, 标志开始解析<springProfile>
		this.depth++;
		// 只能处理外层的<springProfile>, 嵌套直接结束(示例: <springProfile><springProfile>...<springProfile/><springProfile/>)
		if (this.depth != 1) {
			return;
		}
		// 当前action入栈, 待后续使用
		ic.pushObject(this);
		// 记录 当前的<springProfile>是否满足运行的环境情况
		this.acceptsProfile = acceptsProfiles(ic, attributes);
		this.events = new ArrayList<>();
		// 将当前的SpringProfileAction加入InterpretationContext的监听器列表中listenerList. 该监听器用于添加寄存的内嵌标签事件. 不展开
		ic.addInPlayListener(this);
	}

	private boolean acceptsProfiles(InterpretationContext ic, Attributes attributes) {
		if (this.environment == null) {
			return false;
		}
		// 获取<springProfile>属性name, 按","分隔. 不展开
		String[] profileNames = StringUtils
			.trimArrayElements(StringUtils.commaDelimitedListToStringArray(attributes.getValue(NAME_ATTRIBUTE)));
		if (profileNames.length == 0) {
			return false;
		}
		// 获取profileNames的真实值. 不展开
		for (int i = 0; i < profileNames.length; i++) {
			profileNames[i] = OptionHelper.substVars(profileNames[i], ic, this.context);
		}
		// 判断当前的<springProfile>是否满足运行的环境. 不展开
		return this.environment.acceptsProfiles(Profiles.of(profileNames));
	}

	@Override
	public void end(InterpretationContext ic, String name) throws ActionException {
		// 深度减一, 标志结束解析<springProfile>
		this.depth--;
		// 嵌套<springProfile>标签, 直接结束
		if (this.depth != 0) {
			return;
		}
		// 移除掉SpringProfileAction这个监听器
		ic.removeInPlayListener(this);
		// 校验当前action是否为栈顶对象, 是则弹出
		verifyAndPop(ic);
		// 重要代码: 当前<springProfile>有效, 将<springProfile>的内嵌标签事件 丢回正在处理eventList列表中. 插入的位置为下一个执行事件前面
		if (this.acceptsProfile) {
			addEventsToPlayer(ic);
		}
	}

	private void verifyAndPop(InterpretationContext ic) {
		// 校验当前action是否为栈顶对象, 是则弹出. 不展开
		Object o = ic.peekObject();
		Assert.state(o != null, "Unexpected null object on stack");
		Assert.isInstanceOf(SpringProfileAction1.class, o, "logback stack error");
		Assert.state(o == this, "ProfileAction different than current one on stack");
		ic.popObject();
	}

	private void addEventsToPlayer(InterpretationContext ic) {
		// 移除<springProfile>和<springProfile/>事件
		Interpreter interpreter = ic.getJoranInterpreter();
		this.events.remove(0);
		this.events.remove(this.events.size() - 1);
		// 将<springProfile>内嵌事件丢回正在处理eventList列表中. 插入的位置为下一个执行事件前面
		interpreter.getEventPlayer().addEventsDynamically(this.events, 1);
	}

	@Override
	public void inPlay(SaxEvent event) {
		// 使用SpringProfileAction该监听器添加事件到事件寄存器events中. 
		// 调用处: 在EventPlayer的play方法中调用, 将当前的事件添加到各个监听器中去. 不展开
		this.events.add(event);
	}
}

说明:

  1. 正常配置下, 最多只有一个springProfile标签有效(配置的springProfile的name包含了spring.profiles.active)
  2. springProfile内的标签事件不是立即解析的, 是待springProfile标签结束后再处理内嵌的标签事件(springProfile有效的情况下)
  3. 假设当前spring.profiles.active=dev, 则解析标签顺序示例如下图
    在这里插入图片描述
生成式引擎优化(GEO)标准化操作流程:构建高效AI生成系统的全链路指南 GEO是针对生成式AI的内容优化策略,通过提升内容在AI生成答案中的引用优先级实现品牌传播。相比传统SEO,GEO使内容直接嵌入AI答案,无需用户点击链接。核心流程包括:1)逆向拆解AI问答场景,结构化知识资产;2)生产权威内容(白皮书、专利数据)和算法适配内容(PCOE模板);3)技术实现(数据投喂、模型微调);4)331渠道分发策略;5)效果监控与动态防御。需注意采用结构化数据标记、主动提交训练数据,并避免简单复制SEO策略。2025版强调内容权威性和语义优化,通过AI原生内容工程实现高效曝光。 阅读详情

相关推荐

VASP能带计算避坑指南:从CHGCAR读取失败到成功解决的全过程

本文深度解析VASP能带计算中常见的“CHGCAR读取失败”报错,并提供从静态自洽到非自洽计算的全流程避坑指南。核心解决方案包括使用CONTCAR替代POSCAR、调整SYMPREC参数以处理对称性判定问题,并推荐利用vaspkit工具自动化生成输入文件,确保计算流程稳健高效。

weixin_29245767的博客 770

springboot 日志配置(logback)

springboot 日志配置(logback)

墨鸦的博客 1万+

AI驱动的CNC闭环加工系统:边缘实时控制与IIoT反馈实践

闭环控制是工业自动化的核心概念,指通过实时反馈调节系统输出以逼近目标值;其技术原理在于构建短于被控对象时间常数的确定性反馈链路,从而保障动态响应精度与稳定性。在智能制造落地中,AI正从离线分析工具演进为嵌入式实时决策单元,其核心价值在于将工艺知识模型化、控制指令轻量化、执行路径边缘化。典型应用场景包括CNC加工过程中的振动自适应调参、刀具磨损在线补偿及多机协同优化。本实践聚焦FANUC 0i-MF平台,基于Modbus TCP硬实时通信与轻量LSTM+CNN融合模型,实现217毫秒级AI推理与PMC寄存器直

weixin_29704001的博客 322

logback配置载入顺序,配置文件命名优先级

logback配置类的初始化在: org.slf4j.impl.StaticLoggerBinder类中的静态代码块中: 初始化调用了上下文初始化的自动配置方法: 自动配置中的关键代码调用 findURLOfDefaultConfigurationFile,去查找配置文件类: 会通过这四个地方查找logback的配置文件 findConfigFileURLFromSystemProperties函数查找的是: final public static String CONFIG

huang007guo的专栏 3896

YOLO-FastestV2从零部署到移动端推理实战手册

本文详细介绍了YOLO-FastestV2从零开始的完整部署流程,涵盖环境搭建、模型训练、格式转换及移动端部署。通过实战步骤,指导开发者将这一轻量级目标检测模型成功部署到Android设备,实现实时推理。文章重点解析了PyTorch到NCNN的模型转换与优化,助力移动端AI应用开发。

l1k9j8h7g6的博客 668

DCA决策曲线的解读和代码实现

0.决策曲线 决策曲线分析法(Decision Curve Analysis,DCA)是个与ROC曲线相提并论的相对比较新的模型评价方法。 关于它的原理,长篇大论的医学统计学知识解读实在不是我的强项,放上两个链接偷偷懒: https://www.plob.org/article/12455.html https://www.iikx.com/news/statistics/1622.html 如果想看文章,这篇里的fig7就是:https://www.ncbi.nlm.nih.gov/pmc/artic

weixin_42960896的博客 1万+

Spring Boot Logback启动流程

Spring Boot 默认使用的是 Logback 的日志框架、Logback 的组件主要通过 Spring Boot ApplicationListener 启动的 // LoggingApplicationListener @Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationStartingEvent) { onApplicationSt.

CoderLi 949

【气候模式降尺度】分位数增量映射(QDM)原理及MATLAB代码实现

简介QDM原理及其MATLAB代码实现

WW、forever的博客 5859

Slf4j使用Logback时,Logback如何初始化

SLF4J,全称 Simple Logging Facade for Java,是一个用于Java编程语言的日志系统抽象层。它为多种现有日志框架(例如Log4j、java.util.logging等)提供了统一的接口, 但自身并不实现日志功能。SLF4J 允许用户在部署时选择适当的日志库(即日志框架),比如 Logback、Log4j 等。

程序猿的樊笼 1037

ROS Motion Planning运动规划库安装方法及进阶使用方法详细介绍

​今天偶然发现了一个优质的运动规划库:ai-winter/ros_motion_planning,比较适合从事ROS移动机器人运动规划研究领域的小伙伴学习和使用,相比于莱斯大学Kavraki实验室提供的开源的著名运动规划库OMPL、或着我之前介绍过的zhm-real开源的zhm-real/MotionPlanning和zhm-real/PathPlanning运动规划库,今天介绍的ROS Motion Planning运动规划库与ROS机器人中常用的Navigation导航框架的兼容性更好,库中的运动规划

慕羽★的博客 5576

【用unity实现100个游戏之15】开发一个类保卫萝卜的Unity2D塔防游戏5(附项目源码,完结)

本期是本项目的最后一篇,主要内容是配置环境、生成不同敌人、结束重开。

向宇的博客,专注php/web全栈 unity游戏开发,欢迎大家评论纠错 2569

logback源码解读笔记(springboot)

logback源码解读笔记(springboot)一、Logfactory初始化StaticLoggerBinder的初始化二、springboot与logback整合三、logger的执行与日志的打印 本人博客原地址:logback源码解读笔记(springboot) 最近在学习使用同事开发的日志框架,感觉对于logback除了基本的使用并没有很深刻的了解,遂对debug过程做大概记录以备忘,debug所用demo如下:https://gitee.com/eshin/logbackDemo 一、Logf

qq_22351805的博客 4234

ZeroClaw安装、使用方法详细全解

ZeroClaw 是一个用 Rust 编写的轻量级、高性能、完全自主的 AI 助手运行时框架

洛星核的博客 616

(二)Logback-slf4j日志和Springboot集成配置原理

目录 一、前话 二、原理分析 1.LoggingApplicationListener 2.ApplicationEvent 2.1 ApplicationEnvironmentPreparedEvent 2.2 LoggingSystemProperties 2.3 LogFile 2.4 获取早期日志级别以及初始化配置文件 2.5 initializeFinalLoggingLevels方法 3.LogbackLoggingSystem 3.1 beforeInitialize()

Armyyyyy丶的博客 677

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

Logback 日志启动流程

Springboot 启动 logback日志框架加载流程分析

J 博客 1274

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

SpringBoot之logback-spring.xml不生效的解决方法

转自:https://www.jb51.net/article/132619.htm 这篇文章主要介绍了SpringBoot之logback-spring.xml不生效的解决方法,觉得挺不错的,现在分享给大家,也给大家做个参考。一起来看看吧 目录 一、前言 二、问题产生 三、问题分析 四、问题解决 五、总结 一、前言 做新应用就是这样,会遇到各种问题,昨天刚解决了加载某一...

u012501054的博客 1万+

Springboot项目中基于Logback框架自定义日志输出

对Springboot中原有的Logback框架进行封装,通过AOP针对自定义注释进行日志输出,并配置输出的log文件路径,对于某些必要的日志,需要将其信息存储至数据库。

qq_43812006的博客 2364

顿悟!新手都能学懂的SpringBoot源码分析!

概述 日志是一个系统必不可缺少的东西,记录了系统运行时的点点滴滴,便于我们了解自己系统的运行状态,在我们使用 Spring Boot时,默认就已经提供了日志功能,使用Logback作为默认的日志框架。那么,接下来我们依赖来看看 Spring Boot是如何初始化好日志系统的。关于Spring的知识点总结了一个图谱,分享给大家: 为什么Spring Boot默认的日志框架是Logbasck呢? 因为在spring-boot-starter模块中引入spring-boot-starter-logging模块,

程序媛小琬的博客 776

IDEA 2020搭建SpringBoot项目:从环境配置到第一个REST接口

Java Web开发中,项目初始化与环境配置是构建稳定应用的基础。SpringBoot通过“约定大于配置”的理念和自动装配机制,极大地简化了传统Spring应用的搭建流程,其核心价值在于提升开发效率、统一项目结构并降低配置复杂度。在实际工程实践中,开发者通常需要整合JDK、Maven和IDE工具链,并理解依赖管理、项目结构及核心配置文件的作用。本文聚焦于使用IDEA 2020这一经典版本,详细拆解如何从零开始,系统化地搭建一个结构清晰、配置规范的SpringBoot Web应用,涵盖**环境变量配置**、*

weixin_30423065的博客 214

【无标题】

虽然springboot会加载JavaLoggingSystem,Log4J2LoggingSystem,LogbackLoggingSystem三个日志实现类,但在选择时,还是会使用LogbackLoggingSystem作为它的日志框架

mythsmyths的博客 310

SpringBoot项目从Logback迁移到Log4j2后Nacos日志不打印?这个隐藏配置项要关掉

本文深入分析了SpringBoot项目从Logback迁移至Log4j2后,Nacos客户端日志正常但业务日志失效的根本原因。问题源于Nacos客户端内置的日志初始化机制会抢先加载配置,干扰Spring Boot的正常初始化流程。核心解决方案是通过设置系统属性`nacos.logging.default.config.enabled=false`,将日志框架控制权交还给应用,确保Log4j2配置正确生效。

weixin_29279047的博客 333

深入解析Spring Boot日志系统:从核心特性到实战配置

在现代化的Java应用开发中,日志系统扮演着不可或缺的角色。作为Spring Boot框架的核心组件之一,其日志系统通过高度抽象的设计和开箱即用的特性,为开发者提供了强大而灵活的日志管理能力。2025年的今天,随着微服务架构和云原生应用的普及,一套完善的日志解决方案对应用的监控、调试和运维至关重要。

zuiyuelong的博客 774

SpringBoot源码学习之日志

SpringBoot源码分析之日志系统的构造 常用知识点 SpringBoot支持4种类型的日志:(通过classLoader加载,可配置pom.xml决定使用哪个日志框架) JDK内置的Log(JavaLoggingSystem) Log4j(Log4JLoggingSystem) Log4j2(Log4J2LoggingSystem) Logback(LogbackLoggingSystem)【springBoot默认】 接下来以Logback为例介绍配置文件(logback.xml)加载顺序(优先

amingzm123的博客 267

Springboot 中的Log处理

Logback(默认)

专注Java(全栈)应用开发,求知若渴,虚心若愚,talk is cheap, show me the code. 550

springboot 从jar包中分离出lib包,实现lib包与项目代码分离

springboot 从jar包中分离出lib包,实现lib包与项目代码分离

donkeyboy001的博客 3713

【ElasticSearch】Linux搭建ELK,Springboot整合ELK分布式日志

在浏览器打开9200端口地址: (http://120.78.129.95:9200/ (opens new window)),如果出现了下面的信息,就表示已经成功启动了。在浏览器打开5601端口地址: (http://120.78.129.95:5601/ (opens new window)),如果出现了下面的信息,就表示已经成功启动了。下载canal.deployer,可以直接监听MySQL的binlog,把自己伪装成MySQL的从库,只负责接收数据,并不做处理。

weixin_44823875的博客 143

AI一键生成:在InsCode平台实现SpringBoot+Docker自动化部署的最佳实践

传统SpringBoot+Docker部署需要编写大量样板代码,而通过智能生成可以专注业务逻辑- 平台生成的Docker配置考虑了生产级最佳实践,比如多阶段构建、非root用户运行等安全措施- 健康检查和日志等运维关键点默认配置完善,减少了后期调试时间在。

ThunderstormLynx23的博客 1047
上一篇: logback源码浅析
下一篇: Nacos配置中心使用
陈*^_^*
博客等级 码龄6年 48粉丝 16原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值