You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot动态切换日志文件报错原因排查与解决求助

Spring Boot动态切换Log4j2日志文件报错分析与解决

问题场景

在Spring Boot应用中动态修改日志输出文件后,日志能成功写入新文件,但持续触发报错,同时需要保留日志输出到控制台的能力。相关配置、代码及报错信息如下:

日志配置文件(log4j2.xml)

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="ConsoleAppender" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/>
        </Console>
        <File name="FileAppender" fileName="logs/api.log" append="false">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </File>
    </Appenders>
    <Loggers>
        <Root level="INFO">
            <AppenderRef ref="ConsoleAppender" />
            <AppenderRef ref="FileAppender" />
        </Root>
        <Logger name="myPackage" level="DEBUG" additivity="false">
            <AppenderRef ref="ConsoleAppender" />
            <AppenderRef ref="FileAppender" />
        </Logger>
    </Loggers>
</Configuration>

动态切换日志文件的方法

public static void updateLogger(String fileName, String appenderName, String packageName) {
    LoggerContext context = (LoggerContext) LogManager.getContext(false);
    Configuration configuration = context.getConfiguration();
    Layout<? extends Serializable> oldLayout = configuration.getAppender(appenderName).getLayout();

    // 删除旧的appender/logger
    configuration.getAppender(appenderName).stop();
    configuration.removeLogger(packageName);

    // 创建新的appender/logger
    LoggerConfig loggerConfig = new LoggerConfig(packageName, Level.ALL, false);
    FileAppender appender = FileAppender.newBuilder()
            .withFileName(fileName)
            .withAppend(true)
            .setName(appenderName)
            .setLayout(oldLayout)
            .build();
    appender.start();

    loggerConfig.addAppender(appender, Level.ALL, null);
    configuration.addLogger(packageName, loggerConfig);
    context.updateLoggers();
}

方法调用代码

FileUtils.updateLogger("logs/logs/log" + ".txt", "FileAppender", "mypackage");

报错信息

2023-08-09 15:52:23,418 MessageBroker-8 ERROR Attempted to append to non-started appender FileAppender
2023-08-09 15:52:23,422 MessageBroker-8 ERROR Unable to write to stream logs/api.log for appender FileAppender org.apache.logging.log4j.core.appender.AppenderLoggingException: Error writing to stream logs/api.log
    at org.apache.logging.log4j.core.appender.OutputStreamManager.writeToDestination(OutputStreamManager.java:252)
    at org.apache.logging.log4j.core.appender.FileManager.writeToDestination(FileManager.java:277)
    at org.apache.logging.log4j.core.appender.OutputStreamManager.flushBuffer(OutputStreamManager.java:283)
    at org.apache.logging.log4j.core.appender.OutputStreamManager.flush(OutputStreamManager.java:294)
    at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.directEncodeEvent(AbstractOutputStreamAppender.java:217)
    at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.tryAppend(AbstractOutputStreamAppender.java:208)
    at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.append(AbstractOutputStreamAppender.java:199)
    at org.apache.logging.log4j.core.config.AppenderControl.tryCallAppender(AppenderControl.java:161)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppender0(AppenderControl.java:134)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppenderPreventRecursion(AppenderControl.java:125)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppender(AppenderControl.java:89)
    at org.apache.logging.log4j.core.config.LoggerConfig.callAppenders(LoggerConfig.java:675)
    at org.apache.logging.log4j.core.config.LoggerConfig.processLogEvent(LoggerConfig.java:633)
    at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:616)
    at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:552)
    at org.apache.logging.log4j.core.config.AwaitCompletionReliabilityStrategy.log(AwaitCompletionReliabilityStrategy.java:82)
    at org.apache.logging.log4j.core.Logger.log(Logger.java:161)
    at org.apache.logging.log4j.spi.AbstractLogger.tryLogMessage(AbstractLogger.java:2205)
    at org.apache.logging.log4j.spi.AbstractLogger.logMessageTrackRecursion(AbstractLogger.java:2159)
    at org.apache.logging.log4j.spi.AbstractLogger.logMessageSafely(AbstractLogger.java:2142)
    at org.apache.logging.log4j.spi.AbstractLogger.logMessage(AbstractLogger.java:2022)
    at org.apache.logging.log4j.spi.AbstractLogger.logIfEnabled(AbstractLogger.java:1875)
    at org.apache.commons.logging.LogAdapter$Log4jLog.log(LogAdapter.java:266)
    at org.apache.commons.logging.LogAdapter$Log4jLog.info(LogAdapter.java:230)
    at org.springframework.web.socket.config.WebSocketMessageBrokerStats.lambda$initLoggingTask$0(WebSocketMessageBrokerStats.java:128)
    at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.runAndReset$$$capture(FutureTask.java:308)
    at java.util.concurrent.FutureTask.runAndReset(FutureTask.java)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.io.IOException: Stream Closed
    at java.io.FileOutputStream.writeBytes(Native Method)
    at java.io.FileOutputStream.write(FileOutputStream.java:326)
    at org.apache.logging.log4j.core.appender.OutputStreamManager.writeToDestination(OutputStreamManager.java:250)
    ... 33 more

报错原因分析

  1. Root Logger未更新Appender引用:配置中Root Logger关联了FileAppender,但代码仅停止旧Appender、更新自定义Logger,未处理Root Logger的引用。当Root Logger输出日志时,会调用已停止的旧Appender,触发流关闭报错。
  2. 旧Appender未彻底移除:代码仅停止旧Appender,未从Configuration中删除,导致同名新Appender创建后,内部仍存在旧Appender的残留引用,部分Logger会尝试调用已停止的实例。

解决方案

修复后的动态切换方法

public static void updateLogger(String fileName, String appenderName, String packageName) {
    LoggerContext context = (LoggerContext) LogManager.getContext(false);
    Configuration configuration = context.getConfiguration();
    Layout<? extends Serializable> oldLayout = configuration.getAppender(appenderName).getLayout();

    // 1. 彻底移除旧Appender
    Appender oldAppender = configuration.getAppender(appenderName);
    if (oldAppender != null) {
        oldAppender.stop();
        configuration.removeAppender(appenderName);
    }

    // 2. 更新Root Logger,清除旧Appender引用并添加新引用
    LoggerConfig rootConfig = configuration.getRootLogger();
    rootConfig.removeAppender(appenderName);

    // 3. 创建并初始化新Appender
    FileAppender newAppender = FileAppender.newBuilder()
            .withFileName(fileName)
            .withAppend(true)
            .setName(appenderName)
            .setLayout(oldLayout)
            .build();
    newAppender.start();
    configuration.addAppender(newAppender);

    // 4. 更新自定义Logger配置
    LoggerConfig loggerConfig = configuration.getLoggerConfig(packageName);
    if (loggerConfig != null) {
        loggerConfig.removeAppender(appenderName);
    } else {
        loggerConfig = new LoggerConfig(packageName, Level.ALL, false);
        configuration.addLogger(packageName, loggerConfig);
    }
    loggerConfig.addAppender(newAppender, Level.ALL, null);

    // 5. 将新Appender重新关联到Root Logger,保留控制台+文件双输出
    rootConfig.addAppender(newAppender, Level.INFO, null);

    // 6. 刷新日志上下文
    context.updateLoggers();
}

关键修复点说明

  • 彻底清理旧Appender:停止旧Appender后调用configuration.removeAppender(appenderName),避免同名冲突。
  • 同步更新Root Logger:移除Root Logger对旧Appender的引用,并关联新Appender,确保全局日志输出正常。
  • 安全处理Logger实例:先检查自定义Logger是否存在,存在则更新配置,不存在再创建,避免重复添加。

验证效果

修改后,日志会同时输出到控制台和指定的新日志文件,且不会再出现流关闭或Appender未启动的报错。


内容的提问来源于stack exchange,提问作者Magic Developement

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 20:24:51