Java日志需避开java.util.logging的FileHandler缺陷,优先选Log4j2 RollingFileAppender(配append="false"与immediateFlush="true")或调优SLF4J+Logback异步队列,禁用裸Files.write。

用 java.util.logging 写日志到文件太麻烦?别硬扛
标准库的 FileHandler 默认不自动轮转、不压缩、路径出错静默失败,且 SimpleFormatter 输出格式固定难调试。实际项目里直接用它写生产日志,大概率会在凌晨三点被报警叫醒。
-
FileHandler构造时若目录不存在,会抛IOException而不是自动创建——得手动new File("logs").mkdirs() - 日志级别需显式调用
setLevel(),父 logger(如Logger.getGlobal())的级别默认是INFO,子 handler 不继承 - 多个 JVM 实例同时写同一日志文件会冲突,
FileHandler没有跨进程锁机制
Log4j2 的 RollingFileAppender 怎么配才不丢日志
关键在 append="false" 和 immediateFlush="true" 的组合。设为 false 会导致重启后覆盖旧日志;设为 true 但没开 immediateFlush,断电或 kill -9 时最后几条日志就丢了。
%d %p %c{1.} [%t] %m%n
-
fileName是当前活跃日志路径,filePattern是归档命名规则,两者必须不同,否则启动报错 -
TimeBasedTriggeringPolicy和SizeBasedTriggeringPolicy是“或”关系,满足任一即滚动 -
max="30"表示最多保留 30 个归档文件,超量时删最老的——注意磁盘空间监控
SLF4J + Logback 的 AsyncAppender 为什么反而变慢了
异步日志不是加个 wrapper 就万事大吉。默认队列大小是 256,如果日志量突增(比如全链路 trace 日志打开),队列满后会阻塞业务线程,比同步还卡。
- 必须显式配置
queueSize,建议按峰值 QPS × 平均每请求日志行数 × 2 来估算 -
includeCallerData="true"会触发堆栈遍历,CPU 开销翻倍,仅调试时开启 - 异步 appender 下游若接的是
RollingFileAppender,滚动动作仍发生在异步线程里,IO 延迟会影响整个队列消费速度
自定义日志写入:绕过框架直接 Files.write() 安不安全
安全,但只适合极简场景(如启动脚本记录 PID 或单次错误快照)。直接用 NIO 写文件没有缓冲区管理、无并发控制、无编码处理,StandardOpenOption.APPEND 在 Windows 上对大文件性能陡降。
立即学习“Java免费学习笔记(深入)”;
try {
Files.write(Paths.get("logs/startup.log"),
("[" + Instant.now() + "] started\n").getBytes(StandardCharsets.UTF_8),
StandardOpenOption.CREATE, StandardOpenOption.APPEND);
} catch (IOException e) {
// 这里不能只打印 e.printStackTrace() —— 日志系统可能还没初始化
System.err.println("Failed to write startup log: " + e.getMessage());
}
- 每次写都 new 字节数组 + 系统调用,高频写入下 GC 和 syscall 开销明显
- 没做字符集校验,如果字符串含 BOM 或代理对,
String.getBytes()可能截断 - 多线程写同一文件必须自己加
synchronized或用FileChannel.lock(),后者在 NFS 上不可靠
RandomAccessFileAppender 在高吞吐下表现更可控,但前提是把 bufferSize 和 immediateFlush 的取舍想清楚——这俩参数背后是持久性与性能的硬博弈。










