Java-01-基础篇-JDK日志(JUL)
一,JUL
JDK 自带的日志(java.util.logging,简称 JUL)这是 Java 标准库自带的日志框架,无需额外依赖
1.1 基本实现
package com.toast.learn.spring.core.logger.jdk;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @author toast
* @time 2025/9/11
* @remark
*/
public class JDKLoggingSimpleExample {
private static final Logger logger = Logger.getLogger(JdkLoggingExample.class.getName());
public static void main(String[] args) {
logger.info("This is an info message");
logger.warning("This is a warning message");
logger.severe("This is a severe (error) message");
// 可设置日志级别
logger.setLevel(Level.ALL);
}
}
输出结果
> Task :spring-learn:run
9月 11, 2025 6:05:51 下午 com.toast.learn.spring.core.logger.jdk.JDKLoggingExample01 main
信息: This is an info message
9月 11, 2025 6:05:51 下午 com.toast.learn.spring.core.logger.jdk.JDKLoggingExample01 main
警告: This is a warning message
9月 11, 2025 6:05:51 下午 com.toast.learn.spring.core.logger.jdk.JDKLoggingExample01 main
严重: This is a severe (error) message
可以看出,JDK自带的支持国际化,会根据本地的Locale 选择输出对应的日志级别内容
二,日志级别
JDK的日志级别,定义在 java.util.logging.Level 类中,部分代码如下:
package java.util.logging;
public class Level implements Serializable {
private static final String defaultBundle = "sun.util.logging.resources.logging";
private final String name;
private final int value;
private final String resourceBundleName;
private transient String localizedLevelName;
private transient Locale cachedLocale;
public static final Level OFF = new Level("OFF", Integer.MAX_VALUE, "sun.util.logging.resources.logging");
public static final Level SEVERE = new Level("SEVERE", 1000, "sun.util.logging.resources.logging");
public static final Level WARNING = new Level("WARNING", 900, "sun.util.logging.resources.logging");
public static final Level INFO = new Level("INFO", 800, "sun.util.logging.resources.logging");
public static final Level CONFIG = new Level("CONFIG", 700, "sun.util.logging.resources.logging");
public static final Level FINE = new Level("FINE", 500, "sun.util.logging.resources.logging");
public static final Level FINER = new Level("FINER", 400, "sun.util.logging.resources.logging");
public static final Level FINEST = new Level("FINEST", 300, "sun.util.logging.resources.logging");
public static final Level ALL = new Level("ALL", Integer.MIN_VALUE, "sun.util.logging.resources.logging");
private static final Level[] standardLevels;
private static final long serialVersionUID = -8176160795706313070L;
/** 忽略其他代码 */
}
从这里也可以看出日志级别的国际化并没有直接国际化的内容,日志级别的国际化的相关资源都存储在指定的位置。
日志级别的国际化资源包存储位置:"sun.util.logging.resources.logging"
第三个参数是资源包名(ResourceBundle),用于:
本地化级别名称(如中文环境下显示“信息”而不是“INFO”)
提供标准错误消息模板 messages_zh_CN.properties 。

public final class logging_zh_CN extends ListResourceBundle {
public logging_zh_CN() {
}
protected final Object[][] getContents() {
return new Object[][]{
{"ALL", "全部"},
{"CONFIG", "配置"},
{"FINE", "详细"},
{"FINER", "较详细"},
{"FINEST", "非常详细"},
{"INFO", "信息"},
{"OFF", "禁用"},
{"SEVERE", "严重"},
{"WARNING", "警告"}
};
}
}
2.1 完整级别列表(按严重程度从高到低排序)
|
|
|
关闭所有日志,最高“级别”,实际用于禁用日志输出 |
临时关闭日志、性能压测 |
|
|
1000 |
严重错误,系统不可用、关键功能失败(如数据库连接失败、空指针未捕获) |
生产环境必须记录 |
|
|
900 |
警告,潜在问题但系统仍可运行(如配置缺失使用默认值、重试成功) |
需要关注,可能预示未来故障 |
|
|
800 |
信息性消息,记录系统关键节点(如启动完成、用户登录、定时任务触发) |
生产环境默认开启 |
|
|
700 |
配置信息,记录初始化参数、环境配置(如端口、数据源URL、线程池大小) |
调试部署问题、环境差异 |
|
|
500 |
调试信息(基本),方法入口/出口、关键变量值 |
开发/测试环境 |
|
|
400 |
更细粒度调试,循环内部、分支判断、中间状态 |
详细调试复杂逻辑 |
|
|
300 |
最详细调试,每行关键代码、对象内部状态、网络包内容 |
深度排查问题,性能开销大 |
|
|
|
记录所有级别,最低“级别”,用于开启全部日志 |
本地调试、问题复现 |
✅ 数值越小,级别越低,日志越详细
❗ 只有当日志级别 ≥ Logger 设置的级别时,才会被记录
2.2 日志级别输出案例
package com.toast.learn.spring.core.logger.jdk;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @author toast
* @time 2025/9/11
* @remark
*/
public class LogLevelExample {
private static final Logger logger = Logger.getLogger(LogLevelExample.class.getName());
public static void main(String[] args) {
// 设置日志级别为 INFO(默认通常是 INFO)
logger.setLevel(Level.INFO);
logger.severe("数据库连接失败!"); // ✅ 输出(SEVERE >= INFO)
logger.warning("缓存未命中,使用默认值"); // ✅ 输出(WARNING >= INFO)
logger.info("系统启动完成"); // ✅ 输出(INFO >= INFO)
logger.config("监听端口: 8080"); // ❌ 不输出(CONFIG=700 < INFO=800)
logger.fine("进入 doSomething 方法"); // ❌ 不输出
logger.finest("变量 x = 100"); // ❌ 不输出
}
}
输出结果:
9月 11, 2025 6:18:03 下午 com.toast.learn.spring.core.logger.jdk.LogLevelExample main
严重: 数据库连接失败!
9月 11, 2025 6:18:03 下午 com.toast.learn.spring.core.logger.jdk.LogLevelExample main
警告: 缓存未命中,使用默认值
9月 11, 2025 6:18:03 下午 com.toast.learn.spring.core.logger.jdk.LogLevelExample main
信息: 系统启动完成
三,Handler 日志处理
JDK 自带的日志系统(java.util.logging, JUL)完全支持将日志输出到文件,甚至可以配置多个输出目标(如同时输出到控制台和文件)、按大小或时间滚动、设置编码等。
日志的默认输出是控制台,是因为默认配置只启用了 ConsoleHandler。我们可以通过配置 FileHandler 来输出到文件。
package com.toast.learn.spring.core.logger.jdk;
import java.io.IOException;
import java.util.logging.*;
/**
* @author toast
* @time 2025/9/11
* @remark
*/
public class JdkLoggingToFileExample {
private static final Logger logger = Logger.getLogger(JdkLoggingToFileExample.class.getName());
public static void main(String[] args) {
try {
// 创建 logs 目录
java.nio.file.Files.createDirectories(java.nio.file.Paths.get("./logs"));
// 创建 FileHandler
FileHandler fileHandler = new FileHandler("./logs/app.log", 10 * 1024 * 1024, 5, true);
fileHandler.setEncoding("UTF-8");
fileHandler.setLevel(Level.ALL);
// 设置格式
SimpleFormatter formatter = new SimpleFormatter() {
@Override
public String format(LogRecord record) {
return String.format("[%1$tF %1$tT] [%2$-7s] %3$s - %4$s%n",
record.getMillis(),
record.getLevel().getLocalizedName(),
record.getLoggerName(),
record.getMessage());
}
};
fileHandler.setFormatter(formatter);
// 添加到 Logger(建议添加到 root logger)
Logger rootLogger = Logger.getLogger("");
rootLogger.addHandler(fileHandler);
// 可选:关闭父级处理器避免重复(如果不需要控制台输出)
// logger.setUseParentHandlers(false);
logger.info("This is an info message");
logger.warning("This is a warning message");
logger.severe("This is a severe (error) message");
} catch (IOException e) {
e.printStackTrace();
}
}
}
输出结果-控制台:
9月 11, 2025 6:32:24 下午 com.toast.learn.spring.core.logger.jdk.JdkLoggingToFileExample main
信息: This is an info message
9月 11, 2025 6:32:24 下午 com.toast.learn.spring.core.logger.jdk.JdkLoggingToFileExample main
警告: This is a warning message
9月 11, 2025 6:32:24 下午 com.toast.learn.spring.core.logger.jdk.JdkLoggingToFileExample main
严重: This is a severe (error) message
输出结果-文件:

可以发现每一个Handler 日志输出处理都有一个独立日志格式属性。上述代码并且设置控制台的日志格式,但是文件的输出设置日志格式。
JDK 除了提供的控制台,File输出,还提供了MemoryHandler,SocketHandler,StreamHandler,一个是往内存输出,一个是通过网络输出出去,和最后OutputStream 流输出
四,日志格式化
JDK 自带的日志支持日志自定义格式的。
package com.toast.learn.spring.core.logger.jdk;
import java.util.logging.*;
/**
* @author toast
* @time 2025/9/11
* @remark
*/
public class LogFormatExample {
private static final Logger logger = Logger.getLogger(JdkLoggingExample.class.getName());
public static void main(String[] args) {
// 获取控制台处理器
Handler[] handlers = logger.getParent().getHandlers(); // 通常从 root logger 获取
for (Handler handler : handlers) {
if (handler instanceof ConsoleHandler) {
// 创建自定义格式
SimpleFormatter formatter = new SimpleFormatter() {
@Override
public String format(LogRecord record) {
return String.format("[%1$tF %1$tT] [%2$-7s] %3$s - %4$s%n",
record.getMillis(),
record.getLevel().getLocalizedName(),
record.getLoggerName(),
record.getMessage());
}
};
handler.setFormatter(formatter);
}
}
logger.info("This is an info message");
logger.warning("This is a warning message");
logger.severe("This is a severe (error) message");
}
}
输出内容:
[2025-09-11 18:28:48] [信息 ] com.toast.learn.spring.core.logger.jdk.JdkLoggingExample - This is an info message
[2025-09-11 18:28:48] [警告 ] com.toast.learn.spring.core.logger.jdk.JdkLoggingExample - This is a warning message
[2025-09-11 18:28:48] [严重 ] com.toast.learn.spring.core.logger.jdk.JdkLoggingExample - This is a severe (error) message
五,JDK 日志设置挺全面的,为什么流行不起来
你说:“JDK 自带的日志设置得也并不错,为什么会不流行使用?”
✅ 你的观察是对的 —— 从功能上讲,JUL(java.util.logging)确实“不差”:
- 支持级别控制(INFO/WARN/SEVERE…)
- 支持 Handler(输出到控制台、文件、网络等)
- 支持 Formatter(自定义格式)
- 支持 Filter(过滤日志)
- 支持 ResourceBundle(国际化)
- 线程安全、模块化支持、配置文件驱动
但“功能完整” ≠ “好用” ≠ “流行”。
🚫 为什么 JUL 不流行?—— 7 大核心原因
1️⃣ 🧩 配置复杂、不够灵活(最大痛点)
- JUL 依赖
logging.properties,语法老旧、不直观。 - 想按天滚动日志?❌ 不支持,只能按大小。
- 想输出 JSON 格式?❌ 很难,需自定义 Formatter。
- 想异步输出避免阻塞?❌ 无内置支持。
- 想动态修改日志级别(不重启)?❌ 需要自己写代码或依赖 JMX。
🆚 Logback/Log4j2:支持 XML/YAML/JSON 配置、按天/小时滚动、异步日志、热更新、JSON 格式、条件配置等。
2️⃣ 🐢 性能一般,尤其高并发下
- JUL 的 Handler 默认是同步阻塞写入(如写文件、网络)。
- 没有内置异步 Appender(Log4j2 有
AsyncAppender,Logback 有AsyncAppender)。 - 在微服务、高吞吐场景下,容易成为性能瓶颈。
📊 性能对比(粗略):
- Log4j2 (异步) > Logback > JUL
3️⃣ 🧱 扩展性差,生态弱
- 想输出到 Kafka、Elasticsearch、Graylog?❌ JUL 没有官方支持。
- 想集成 MDC(Mapped Diagnostic Context,如 traceId)?❌ 需要自己封装。
- 社区几乎没有第三方 Handler/Formatter 插件。
🆚 Logback/Log4j2:海量插件、开箱即用、社区活跃。
4️⃣ 🤝 与主流框架集成差
- Spring Boot 默认用 Logback,不加载 JUL 的
logging.properties。 - 如果你想在 Spring Boot 里用 JUL,需要手动排除默认日志、桥接、配置 —— 非常麻烦。
- 大部分开源库(如 Hibernate、Netty、Apache Commons)默认使用 SLF4J —— 如果你用 JUL,需要桥接(
jul-to-slf4j),增加复杂度。
✅ SLF4J + Logback 成为事实标准 → JUL 被边缘化。
5️⃣ 📜 API 设计不够友好
JUL 的 API 冗长:
- java1Loggerlogger = Logger.getLogger(MyClass.class.getName()); // 冗长
vs SLF4J:
- java1Loggerlogger = LoggerFactory.getLogger(MyClass.class); // 简洁
不支持 {} 占位符(容易造成字符串拼接性能浪费):
- java1234⌄// JUL:必须手动判断是否启用if (logger.isLoggable(Level.FINE)) { logger.fine("User " + name + " logged in at " + time); // 性能差!}
vs SLF4J:
- java1logger.debug("User {} logged in at {}", name, time); // 自动判断,无性能损耗
6️⃣ 🌐 历史包袱 & 社区惯性
- JUL 是 2002 年随 JDK 1.4 推出的,设计较早。
- 2006 年 Log4j 作者又推出 SLF4J + Logback,设计更现代、性能更好。
- 2014 年 Log4j2 诞生,性能碾压 JUL。
- 开发者一旦习惯 SLF4J,就不会回头用 JUL —— 生态一旦形成,很难逆转。
📈 趋势:JUL → Log4j → SLF4J + Logback/Log4j2
7️⃣ 🛠 调试和运维不友好
JUL 默认输出格式不统一、不美观:
- 129月 11, 2025 5:07:41 下午 com.example.MyClass main信息: This is an info message
Logback/Log4j2 默认格式清晰、可读性强:
- 12025-09-11 17:07:41.123 [main] INFO com.example.MyClass - This is an info message
- 缺少“开箱即用”的生产级配置(如按天滚动、压缩归档、错误分离等)。
✅ 什么时候适合用 JUL?
虽然不推荐新项目使用,但以下场景 JUL 仍有价值:
|
JDK 工具/小型工具类 |
不想引入第三方依赖,JUL 零依赖 |
|
Android 开发(早期) |
早期 Android 支持 JUL(现在推荐 Timber + SLF4J) |
|
阅读 JDK 源码 |
JDK 内部大量使用 JUL,理解它有助于阅读源码 |
|
维护老项目 |
有些老系统仍在用 JUL,需要维护 |
📊 流行度对比(2025 年)
|
SLF4J + Logback |
✅✅✅ 80%+ |
Spring Boot 默认,生态最成熟 |
|
SLF4J + Log4j2 |
✅✅ 15% |
性能最强,适合高并发 |
|
JUL (java.util.logging) |
❌ <5% |
仅用于 JDK 内部或老项目 |
|
Log4j 1.x |
❌ 已淘汰 |
安全漏洞,不再推荐 |
📉 JUL 的使用率逐年下降,尤其在云原生、微服务架构下几乎绝迹。
💡 总结:为什么“功能不差”却不流行?
✍️ “JUL 是一辆功能齐全的老爷车 —— 有方向盘、有刹车、能跑,但没人想开它上高速。”
- 它诞生太早,设计不够现代。
- 性能、扩展性、易用性被后起之秀全面超越。
- 生态和社区惯性让它逐渐边缘化。
更多推荐



所有评论(0)