Spring Boot中ConfigurationSource初始化Log4j2配置失败问题排查
Spring Boot项目中Log4j2从数据库加载配置(ConfigurationSource方式失效问题)
我正在开发一个使用Log4j2进行日志记录的Spring Boot项目,Log4j2.xml配置文件存储在数据库中,需先从数据库获取后再使用。配置内容如下:
<?xml version="1.0" encoding="UTF-8"?> <Configuration> <!-- setting --> <Properties> <Property name="merchantdb">/syslog/translog/merchantdb/</Property> <Property name="pattern1">%d{yyyy-MM-dd HH:mm:ss}[%5p][%C{2}][%t] %m%n</Property> <Property name="pattern2">%d{yyyy-MM-dd HH:mm:ss}[%5p][%C{2}.%M()] %m%n</Property> </Properties> <!-- appender setting --> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="${pattern1}"/> </Console> <RollingFile name="merchantdb" filePattern="${merchantdb}/merchantdb.log.%d{yyyy-MM-dd}"> <PatternLayout pattern="${pattern1}"/> <Policies> <TimeBasedTriggeringPolicy interval="1"/> </Policies> <DirectWriteRolloverStrategy/> </RollingFile> <RollingFile name="merchantdb_io" filePattern="${merchantdb}/merchantdb_io.log.%d{yyyy-MM-dd}"> <PatternLayout pattern="${pattern2}"/> <Policies> <TimeBasedTriggeringPolicy interval="1"/> </Policies> <DirectWriteRolloverStrategy/> </RollingFile> <RollingFile name="merchantdb_i" filePattern="${merchantdb}/merchantdb_i.log.%d{yyyy-MM-dd}"> <PatternLayout pattern="${pattern2}"/> <Policies> <TimeBasedTriggeringPolicy interval="1"/> </Policies> <DirectWriteRolloverStrategy/> </RollingFile> <RollingFile name="merchantdb_o" filePattern="${merchantdb}/merchantdb_o.log.%d{yyyy-MM-dd}"> <PatternLayout pattern="${pattern2}"/> <Policies> <TimeBasedTriggeringPolicy interval="1"/> </Policies> <DirectWriteRolloverStrategy/> </RollingFile> <RollingFile name="rmds" filePattern="${merchantdb}/rmds.log.%d{yyyy-MM-dd}"> <PatternLayout pattern="${pattern2}"/> <Policies> <TimeBasedTriggeringPolicy interval="1"/> </Policies> <DirectWriteRolloverStrategy/> </RollingFile> <Socket name="socket" host="10.77.249.230" port="20002" reconnectionDelay="5000" immediateFlush="true"> <JsonLayout compact="true" includeStacktrace="true" charset="UTF-8" eventEol="true" locationInfo="true" objectMessageAsJsonObject="true" stacktraceAsString="true"> <KeyValuePair key="hostName" value="$${hostName}"/> </JsonLayout> </Socket> </Appenders> <Loggers> <Logger name="merchantdb" level="all" additivity="false"> <AppenderRef ref="merchantdb"/> <AppenderRef ref="Console"/> <AppenderRef ref="socket"/> </Logger> <Logger name="merchantdb_io" level="all" additivity="false"> <AppenderRef ref="merchantdb_io"/> <AppenderRef ref="Console"/> <AppenderRef ref="socket"/> </Logger> <Logger name="merchantdb_i" level="all" additivity="false"> <AppenderRef ref="merchantdb_i"/> <AppenderRef ref="Console"/> <AppenderRef ref="socket"/> </Logger> <Logger name="merchantdb_o" level="all" additivity="false"> <AppenderRef ref="merchantdb_o"/> <AppenderRef ref="Console"/> <AppenderRef ref="socket"/> </Logger> <Logger name="com.alibaba.druid.pool" level="all" additivity="false"> <AppenderRef ref="rmds"/> </Logger> </Loggers> </Configuration>
我了解有多种初始化该配置文件的方式,通过文件路径初始化可以正常工作,但使用ConfigurationSource初始化时,配置似乎未生效,日志仍以默认格式输出。以下是我的代码,请问我遗漏了什么?
@Configuration public class ConfigLoader { public static Logger logger; @Bean @DependsOn("ds_settingsDB") public Logger log4j2() throws IOException { System.out.println("loading Log4j2"); FileWriter fw = null; LoggerContext context = null; try { String fieldvalue = SettingsDB.get("Log4J2.xml"); //initBySource(fieldvalue); //this doesn't works initByFilePath(fieldvalue); //this works logger = LogManager.getFormatterLogger("merchantdb"); logger.info("log4j2 load complete"); return logger; }catch(Exception e) { throw new EipException(e); } finally { if(fw!=null){ fw.close(); } } } private LoggerContext initBySource(String fieldvalue) throws Exception { InputStream in = new ByteArrayInputStream(fieldvalue.getBytes("utf-8")); ConfigurationSource source = new ConfigurationSource(in); LoggerContext ctx = Configurator.initialize(null, source); return ctx; } private LoggerContext initByFilePath(String fieldvalue) throws Exception { FileWriter fw = null; LoggerContext ctx = null; String filePath = "C:\\apconf\\log4j2zzz.xml"; fw = new FileWriter(filePath); fw.write(fieldvalue); fw.flush(); File file = new File(filePath); if(file.exists()){ ctx = Configurator.initialize(null, filePath); } return ctx; } }
问题原因及解决方案
1. 未关闭并替换原有LoggerContext
Log4j2启动时会自动初始化默认上下文,直接调用Configurator.initialize传入ConfigurationSource时,新上下文不会自动替换原有上下文,导致日志仍使用默认配置。需要先停止原有上下文,再初始化新的:
修改initBySource方法:
private LoggerContext initBySource(String fieldvalue) throws Exception { // 获取并停止原有默认上下文 LoggerContext oldCtx = (LoggerContext) LogManager.getContext(false); oldCtx.stop(); // 使用try-with-resources确保输入流自动关闭 try (InputStream in = new ByteArrayInputStream(fieldvalue.getBytes(StandardCharsets.UTF_8))) { ConfigurationSource source = new ConfigurationSource(in); // 指定当前类加载器,避免类加载冲突 LoggerContext ctx = Configurator.initialize(null, Thread.currentThread().getContextClassLoader(), source); ctx.start(); return ctx; } }
2. Spring Boot日志初始化时机冲突
Spring Boot会在启动早期自动加载日志配置,你的ConfigLoader Bean可能在日志系统初始化之后执行,导致新配置无法覆盖默认配置。可以通过以下方式解决:
- 在
application.properties中禁用Spring Boot默认日志配置加载:
logging.config=classpath:none
- 添加启动监听器,在Spring环境准备完成后立即加载数据库中的Log4j2配置:
@Component public class Log4j2ConfigInitializer implements ApplicationListener<ApplicationEnvironmentPreparedEvent> { @Override public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { try { String fieldvalue = SettingsDB.get("Log4J2.xml"); LoggerContext oldCtx = (LoggerContext) LogManager.getContext(false); oldCtx.stop(); try (InputStream in = new ByteArrayInputStream(fieldvalue.getBytes(StandardCharsets.UTF_8))) { ConfigurationSource source = new ConfigurationSource(in); Configurator.initialize(null, Thread.currentThread().getContextClassLoader(), source); } } catch (Exception e) { throw new RuntimeException("加载Log4j2配置失败", e); } } }
3. 字符编码与流处理问题
原代码中硬编码"utf-8",建议使用StandardCharsets.UTF_8常量避免拼写错误;同时使用try-with-resources自动关闭输入流,防止资源泄漏。
内容的提问来源于stack exchange,提问作者Albert
相关产品推荐
相关产品推荐

