| 每次重启服务后日志才写入文件,通常是日志框架的缓冲策略、配置错误、权限问题或 Appender 未正确触发导致的实时写入失效,重启时触发了缓冲刷新或配置重新加载才完成写入。以下是具体原因及解决方案: Logback/Log4j2 等框架为了性能,默认会对文件输出做内存缓冲(批量写入磁盘),而非实时刷新。如果缓冲未达到阈值(如大小、时间),日志会暂存内存,重启时 JVM 关闭才强制刷新到文件,导致 “只有重启才看到日志” 的现象。 FileAppender/RollingFileAppender 默认配置中,immediateFlush属性为false(或未显式开启),且缓冲大小 / 时间未到触发条件。 在 XML 配置中显式开启实时刷新,并调整缓冲参数:
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/app.log</file>
<immediateFlush>true</immediateFlush>
<encoder>...</encoder>
<rollingPolicy>...</rollingPolicy>
<bufferSize>1KB</bufferSize>
</appender>
Log4j2 的配置类似,需开启immediateFlush: <RollingFile name="FILE" fileName="${LOG_PATH}/app.log" filePattern="..." immediateFlush="true">
...
</RollingFile>
如果配置了日志滚动(如按日期 / 大小分割),但滚动策略的触发条件未满足,日志可能暂存于临时缓冲或未正确路由到目标文件,重启时策略重新计算才触发写入。 - TimeBasedRollingPolicy 的时间格式错误:比如
fileNamePattern用了%d{yyyy-MM-dd HH}但实际按天分割,导致时间粒度不匹配,触发时机延迟; - SizeAndTimeBasedFNATP 的
maxFileSize设置过大:比如设为 1GB,而实时日志量远未达到,导致日志一直堆积在主文件的缓冲中; - 滚动策略未关联到 Appender:比如漏配
<rollingPolicy>或<triggeringPolicy>,导致滚动失效。
修正滚动策略配置,确保触发条件合理:
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/app.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>30</maxHistory>
</rollingPolicy>
应用运行的用户(如 Tomcat 用户、普通用户)没有日志目录的写入权限或日志文件的修改权限,导致实时写入失败(日志框架会静默失败,不会抛错),重启时可能因用户 / 权限上下文变化(如 sudo 启动)才获得权限,完成写入。 - 检查日志目录的权限:
- 检查日志文件的权限:
ls -l /path/to/logs/app.log
给日志目录 / 文件赋予正确权限:
chown -R appuser:appuser /path/to/logs/
chmod -R 755 /path/to/logs/
配置中虽然定义了文件 Appender,但未将 Logger(或 Root Logger)关联到该 Appender,导致日志只输出到控制台,重启时配置加载异常 “意外” 关联成功(或用户误判)。 - 漏写
<appender-ref ref="FILE"/>; additivity="false"导致日志未传递到 Root Logger 的 FileAppender。
确保 Logger/Root 正确关联 FileAppender:
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
<logger name="com.example.controller" level="DEBUG" additivity="true">
<appender-ref ref="FILE"/>
</logger>
如果使用了AsyncAppender(异步日志),但配置了不合理的队列参数(如队列满、无丢弃策略),导致日志堆积在内存队列中,重启时队列刷出才写入文件。 queueSize过小且neverBlock="true",导致日志被丢弃;- 未配置
discardingThreshold,队列满后阻塞但未处理。
修正 AsyncAppender 配置: <appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE"/>
<queueSize>1024</queueSize>
<discardingThreshold>0</discardingThreshold>
<neverBlock>false</neverBlock>
<includeCallerData>false</includeCallerData>
</appender>
<root level="INFO">
<appender-ref ref="ASYNC_FILE"/>
</root>
- 配置文件未开启热扫描:Logback 默认不开启配置热加载,若修改配置后未重启,旧配置(如未关联 FileAppender)仍生效,重启后新配置才加载;
- Spring Profile 不匹配:若用
springProfile标签指定环境(如<springProfile name="prod">),但运行时未激活对应 Profile,导致 FileAppender 未生效,重启时激活 Profile 才生效。
- Logback 开启配置热扫描:
<configuration scan="true" scanPeriod="30 seconds">
...
</configuration>
- 确保运行时激活正确的 Profile:
java -jar your-app.jar --spring.profiles.active=prod
若日志文件路径用了相对路径(如./logs/app.log),应用的 ** 工作目录(Working Directory)** 可能不是预期目录,导致日志实际写入到其他位置(如 Tomcat 的 bin 目录),用户误以为 “没写入”,重启时工作目录变化才写入到预期位置。 在代码中打印日志文件的绝对路径(以 Logback 为例): import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.core.FileAppender;
public class LogPathChecker {
public static void printLogFilePath() {
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
FileAppender<?> fileAppender = (FileAppender<?>) loggerContext.getLogger("ROOT").getAppender("FILE");
if (fileAppender != null) {
System.out.println("日志文件绝对路径:" + fileAppender.getFile());
}
}
}
使用绝对路径配置日志文件: <property name="LOG_PATH" value="/var/logs/my-app"/>
<file>${LOG_PATH}/app.log</file>
- 检查日志框架的
immediateFlush是否开启; - 验证日志目录 / 文件的权限;
- 确认 Appender 与 Logger 的关联是否正确;
- 检查滚动策略 / 异步 Appender 的配置;
- 打印日志文件绝对路径,确认写入位置;
- 确保运行环境 Profile 与配置匹配。
通过以上步骤,基本能定位 “重启才写入日志” 的根源,核心是解决实时刷新、权限、配置关联这三类问题。 |