JUL默认只输出到控制台,需显式添加FileHandler并确保路径权限、层级继承、Level匹配及Formatter配置正确,否则日志静默丢失。

Logger输出不到文件?默认只打到控制台
Java自带的java.util.logging(JUL)默认只用ConsoleHandler,不写文件——这是最常被忽略的前提。你调了logger.info()却找不到日志文件,不是代码写错了,是根本没配Handler。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 必须显式创建
FileHandler并addHandler()到Logger,JUL不会自动帮你做 - Logger层级继承:如果给
Logger.getLogger("com.example")加了FileHandler,它的子Logger(如"com.example.service")会自动继承,但根Logger("")的Handler不影响自定义Logger,除非你调用setUseParentHandlers(true) - 注意权限:Windows下路径含中文或空格、Linux下目录无写权限,会导致
java.io.IOException: Permission denied,建议先确保new File("logs/app.log").getParentFile().mkdirs()
FileHandler构造参数怎么选?路径和模式最关键
FileHandler构造函数有多个重载,最容易踩坑的是FileHandler(String pattern, boolean append)这个两参数版本——它不支持自动轮转,且pattern里不能带%u或%g占位符(那些只在四参数版本生效)。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 要轮转日志(比如每天一个文件),必须用四参数构造:
new FileHandler("logs/app.%g.log", 1024 * 1024, 5, true),其中%g是索引,5是最大文件数,true表示追加 - 路径建议用绝对路径或基于
System.getProperty("user.dir")拼接,避免相对路径在IDE/打包后失效 - 如果只想要单文件且不轮转,用两参数版即可,但记得手动确保父目录存在,否则抛
IOException
日志格式太简陋?Formatter决定内容可读性
JUL默认用java.util.logging.SimpleFormatter,输出只有时间、级别、类名和消息,没有线程名、方法名、毫秒级时间戳——排查并发问题时基本不够用。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 换用
java.util.logging.XMLFormatter可得结构化输出,但人类阅读不友好;更常用的是自定义Formatter,继承Formatter重写format(),重点处理record.getMillis()、record.getSourceMethodName()等字段 - 别直接改根Logger的Formatter:
Logger.getLogger("").getHandlers()[0].setFormatter(...)会影响所有未单独设置Formatter的Logger,容易误伤 - 如果用了Log4j或SLF4J桥接,JUL的Formatter配置可能被覆盖,此时应优先查桥接层配置
为什么设置了FileHandler还是没日志?检查Level和Filter
即使Handler加对了、路径也合法,日志仍可能“消失”——大概率是Logger或Handler自身的Level设得太高,或者被Filter拦截了。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- Logger默认Level是
INFO,Handler默认Level也是INFO,但如果你调过logger.setLevel(Level.WARNING),那INFO消息就进不了Handler,哪怕Handler Level是ALL - Handler的Level独立于Logger:可以设Logger为
INFO,只让某个FileHandler响应WARNING以上,用于分离错误日志 - 检查是否无意中加了
handler.setFilter(...),尤其是第三方库(如某些监控SDK)可能偷偷注册Filter
真正麻烦的不是写几行配置,而是JUL的层级继承、Level双控、Handler生命周期这些隐式规则——它们不会报错,只会静默丢日志。动手前先用logger.getHandlers()和logger.getLevel()打个断点,比反复猜路径更省时间。











