应使用Log4j2等成熟日志框架而非手动文件写入,因其支持线程安全、异步、滚动归档与自动压缩;配置log4j2.xml可实现按天/大小切分、保留7个归档;代码中通过Logger.info()等API记录日志,支持占位符和异常自动堆栈。
Java 中写日志不等于直接用 FileWriter 或 PrintStream 往文件里塞字符串——那样既难维护,又容易在多线程、滚动、异步等场景下出问题。真正可靠的做法是用成熟日志框架,比如 Log4j2 或 SLF4J + Logback。
手动打开 FileOutputStream、加锁、格式化时间、处理换行、判断文件大小、切分归档……这些逻辑看似简单,实则极易出错:
Appender)failures 回退策略)SimpleDateFormatter 线程不安全)这是最轻量、无需代码侵入的落地方式。把以下内容存为 src/main/resources/log4j2.xml:
%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n
关键点:
fileName 是当前活跃日志路径;filePattern 定义归档名,支持压缩(.gz)TimeBasedTriggeringPolicy 按天滚动;SizeBasedTriggeringPolicy 单文件超 10MB 就切max="7" 表示最多保留 7 个历史归档,超出自动删除logs/ 目录,无需提前 mkdir
不用 new 任何流,只用标准 API:
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger;public class MyApp { private static final Logger logger = LogManager.getLogger(MyApp.class);
public void doSomething() { logger.info("User login: {}", "alice"); logger.error("DB connection failed", new SQLException("Connection refused")); } }
注意:
{} 比字符串拼接更高效(异常堆栈不计算,除非日志级别开启)e.printStackTrace()
MyApp.class 仍能准确绑定日志器,避免硬编码字符串几个高频翻车点:
log4j-core 但漏掉 log4j-api → 报 NoClassDefFoundError: org.apache.logging.log4j.spi.AbstractLogger
log4j:log4j 旧包log4j2.xml → 检查是否在 target/classes 下,不是 src/main/resources 路径不对AsyncLogger 标签,没加 JVM 参数 -Dlog4j2.contextSelector=org.apa
che.logging.log4j.core.async.AsyncLoggerContextSelector → 异步不生效日志框架的配置粒度很细,但核心原则就一条:让日志行为可预测、可收敛、可追溯。手写文件操作在 Demo 里跑得通,在生产环境里大概率成为第一个被拔掉的模块。