Logger 禁止直接 new,必须通过工厂方法获取以确保单例和线程安全;JUL 中同名 getLogger 返回同一实例,自定义 Handler/Formatter 才需谨慎控制共享;FileHandler 需显式设 append=true 并确保目录存在;SLF4J+Logback 是更优实践。

为什么不能直接 new Logger()
Logger 是典型的状态共享对象,内部维护缓冲区、输出目标、日志级别等状态;多个实例会导致日志错乱、文件句柄重复打开、线程安全问题。Java 标准库(如 java.util.logging.Logger)和主流框架(SLF4J/Log4j)都明确禁止手动 new 实例,必须通过工厂方法获取。
常见错误现象:java.io.IOException: Stream closed、日志写入丢失、多线程下文件内容重叠。
- 所有 Logger 获取必须走
Logger.getLogger(String name)或 SLF4J 的LoggerFactory.getLogger(Class) - 名字(name)决定层级继承关系,推荐用类名(
getClass().getName()),避免硬编码字符串 - 不要在每次方法调用里重复获取——虽然它本身是轻量的,但频繁调用
getLogger仍有哈希查找开销
如何确保单例语义不被破坏(以 JUL 为例)
java.util.logging.Logger 本身已实现单例:同名 getLogger 返回同一实例。但很多人误以为要自己套一层“饿汉/懒汉单例”,这反而引入冗余封装,还可能破坏 JUL 的层级配置机制(比如父 Logger 的 Handler 被子 Logger 自动继承)。
真正需要控制单例的,是自定义 Handler 或 Formatter——它们不是线程安全的,且不应被多个 Logger 共享(除非你明确设计为共享)。
立即学习“Java免费学习笔记(深入)”;
- 不要写
public class MyLogger { private static final Logger instance = ... }这类包装 - 如果需定制行为(如统一添加 trace ID),应在
Formatter.format(LogRecord)中处理,而非替换 Logger 实例 - 若必须封装,只封装配置逻辑(如
initLogging()),不封装Logger实例本身
FileHandler 追加写入的关键参数与坑
默认 FileHandler 是覆盖模式,追加需显式指定。但光设 append=true 不够——路径不存在时会创建,但父目录不存在则抛 IOException;同时,多个 JVM 进程不能共用同一 FileHandler,否则文件损坏。
常见错误现象:java.io.FileNotFoundException: logs/app.log (No such file or directory)、日志突然中断、文件大小停滞在 0 字节。
- 初始化前确保目录存在:
new File("logs/").mkdirs() - 构造
FileHandler时传append=true:new FileHandler("logs/app.log", true) - 避免使用相对路径(如
"app.log"),JVM 工作目录不可控;统一用绝对路径或基于System.getProperty("user.dir")拼接 - 务必调用
setFormatter(new SimpleFormatter()),否则默认输出二进制格式,人类不可读
SLF4J + Logback 的更合理实践(非强制但推荐)
纯 JUL 配置繁琐、功能弱(如无自动日志滚动)。SLF4J 是门面,Logback 是原生实现,二者组合才是现代 Java 日志的事实标准。它天然支持异步、按大小/时间滚动、条件过滤,且无需手动管理单例。
关键点在于依赖和配置分离:代码只依赖 slf4j-api,运行时绑定 logback-classic,配置全由 logback.xml 控制。
- 引入 Maven 依赖:
org.slf4j:slf4j-api+ch.qos.logback:logback-classic - Logger 获取:直接
LoggerFactory.getLogger(getClass()),无需关心单例——Logback 内部已优化 - 追加写入:在
logback.xml的<appender></appender>中设置<append>true</append>(默认就是 true) - 别在代码里调用
Logger.setLevel()—— 应该在配置文件中统一管控
最易被忽略的是日志路径权限和磁盘空间:生产环境常因 Permission denied 或 No space left on device 导致静默失败。建议启动时校验日志目录可写,并在 FileAppender 中配置 <prudent>false</prudent>(多 JVM 场景才设 true,但性能差)。










