写点什么

一文详解 Java 日志框架 JUL

  • 2022 年 4 月 26 日
  • 本文字数:8980 字

    阅读完需:约 29 分钟

本文分享自华为云社区《Java 日志框架 JUL 详解大全》,作者: 陈皮的 JavaLib 。

JUL 简介

JUL(Java util logging),Java 原生日志框架,不需要引入第三方依赖包,使用简单方便,一般在小型应用中使用,主流项目中现在很少使用了。

JUL 架构



  • Application:Java 应用程序。

  • Logger:记录器,Java 应用程序通过调用记录器的方法来发布日志记录。

  • Handler:处理器,每一个 Logger 都可以关联一个或多个 Handler,Handler 从 Logger 获取日志并将日志输出到某个目的地,目的地可以是控制台,本地文件,或网络日志服务,或将它们转发到操作系统日志等等。通过Handler.setLevel(level.off)方法可以禁用一个 Handler,也可以设置其他级别来开启此 Handler。

  • Filter:过滤器,根据条件过滤哪些日志记录。每一个 Logger 和 Handler 都可以关联一个 Filter。

  • Formatter :格式化器,负责对日志事件中的日志记录进行转换和格式化。

  • Level:每一条日志记录都有一个关联的日志级别,表示此条日志的重要性和紧急程度。也可以对 Logger 和 Handler 设置关联的日志级别。

入门示例

package com.chenpi;
import java.util.logging.Level;import java.util.logging.Logger;import org.junit.Test;
/** * @author 陈皮 * @version 1.0 * @description * @date 2022/3/2 */public class ChenPiJULMain {
// JUL 日志框架演示 @Test public void testLog() {
// 获取日志记录器对象 Logger logger = Logger.getLogger("com.chenpi.ChenPiJULMain");
// 日志记录输出 logger.severe(">>>>> Hello ChenPi!!"); logger.warning(">>>>> Hello ChenPi!!"); logger.info(">>>>> Hello ChenPi!!"); // 默认日志级别 logger.config(">>>>> Hello ChenPi!!"); logger.fine(">>>>> Hello ChenPi!!"); logger.finer(">>>>> Hello ChenPi!!"); logger.finest(">>>>> Hello ChenPi!!");
// 输出指定日志级别的日志记录 logger.log(Level.WARNING, ">>>>> Hello Warning ChenPi!!");
// 占位符形式 String name = "ChenPi"; int age = 18; logger.log(Level.INFO, ">>>>> Hello {0},{1} years old!", new Object[]{name, age});
// 异常堆栈信息 logger.log(Level.SEVERE, ">>>>> Hello NPE!", new NullPointerException());
}}
复制代码

JUL 默认将日志信息输出到控制台,默认日志级别是 info。控制台输出结果如下:



Logger 父子继承关系

在 JUL 中,Logger 有父子继承关系概念,会根据 Logger 对象的名称的包含关系,划分父子继承关系。对于某个 Logger 对象,如果找不到显性创建的父 Logger 对象,那么它的父 Logger 是根 Logger,即 RootLogger。


子代 Logger 对象会继承父 Logger 的配置,例如日志级别,关联的 Handler,日志格式等等。对于任何 Logger 对象,如果没对其做特殊配置,那么它最终都会继承 RootLogger 的配置。

package com.chenpi;
import java.util.logging.Logger;import org.junit.Test;
/** * @author 陈皮 * @version 1.0 * @description * @date 2022/3/2 */public class ChenPiJULMain {
@Test public void testLog() {
// logger1的父Logger是RootLogger Logger logger1 = Logger.getLogger("com.chenpi.a"); // logger2的父Logger是logger1 Logger logger2 = Logger.getLogger("com.chenpi.a.b.c");
Logger logger1Parent = logger1.getParent(); System.out.println("logger1Parent:" + logger1Parent + ",name:" + logger1Parent.getName());
Logger logger2Parent = logger2.getParent(); System.out.println("logger1:" + logger1 + ",name:" + logger1.getName()); System.out.println("logger2Parent:" + logger2Parent + ",name:" + logger2Parent.getName());
}}
// 输出结果如下logger1Parent:java.util.logging.LogManager$RootLogger@61e717c2,name:logger1:java.util.logging.Logger@66cd51c3,name:com.chenpi.alogger2Parent:java.util.logging.Logger@66cd51c3,name:com.chenpi.a
复制代码

日志配置

我们可以通过 2 种方式调整 JUL 默认的日志行为(设置日志级别,日志输出目的地,日志格式等等),一种是通过在程序中硬编码形式(不推荐),另一种是通过单独的配置文件形式。

硬编码日志配置

在 JUL 中,Logger 是有父子继承关系的,所以当我们需要对某一个 Logger 对象进行单独的配置时,需要将它设置为不继承使用父 Logger 的配置。

以下演示名称为com.chenpi.ChenPiJULMain的 Logger 对象单独进行配置,并且关联两个 Handler。

package com.chenpi;
import java.io.IOException;import java.util.logging.*;import org.junit.Test;
/** * @author 陈皮 * @version 1.0 * @description * @date 2022/3/2 */public class ChenPiJULMain {
@Test public void testLog() throws IOException {
// 获取日志记录器对象 Logger logger = Logger.getLogger("com.chenpi.ChenPiJULMain");
// 关闭默认配置,即不使用父Logger的Handlers logger.setUseParentHandlers(false);
// 设置记录器的日志级别为ALL logger.setLevel(Level.ALL);
// 日志记录格式,使用简单格式转换对象 SimpleFormatter simpleFormatter = new SimpleFormatter();
// 控制台输出Handler,并且设置日志级别为INFO,日志记录格式 ConsoleHandler consoleHandler = new ConsoleHandler(); consoleHandler.setLevel(Level.INFO); consoleHandler.setFormatter(simpleFormatter);
// 文件输出Handler,并且设置日志级别为FINE,日志记录格式 FileHandler fileHandler = new FileHandler("./jul.log"); fileHandler.setLevel(Level.FINE); fileHandler.setFormatter(simpleFormatter);
// 记录器关联处理器,即此logger对象的日志信息输出到这两个Handler进行处理 logger.addHandler(consoleHandler); logger.addHandler(fileHandler);
// 日志记录输出 logger.severe(">>>>> Hello ChenPi!!"); logger.warning(">>>>> Hello ChenPi!!"); logger.info(">>>>> Hello ChenPi!!"); logger.config(">>>>> Hello ChenPi!!"); logger.fine(">>>>> Hello ChenPi!!"); logger.finer(">>>>> Hello ChenPi!!"); logger.finest(">>>>> Hello ChenPi!!"); }}
复制代码

因为 Logger 设置的日志级别是 ALL,即所有级别的日志记录都可以通过。但是 ConsoleHandler 设置的日志级别是 INFO,所以控制台只输出 INFO 级别以上的日志记录。而 FileHandler 设置的日志级别是 FINE,所以日志文件中输出的是 FINE 级别以上的日志记录。

以下是控制台输出的日志结果:



以下是日志文件jul.log中输出的日志结果:



日志配置文件

通过 debug 调试,按以下方法顺序,可以发现,如果我们没有配置 JUL 的配置文件,系统默认从 JDK 的安装目录下的 lib 目录下读取默认的配置文件logging.properties

getLogger()  -> demandLogger() -> LogManager.getLogManager() -> ensureLogManagerInitialized() -> owner.readPrimordialConfiguration() -> readConfiguration()
复制代码



以下是 JDK 自带的 JUL 默认日志配置文件内容:



对于配置选项有哪些,其实可以通过相应类的源码找出,例如 Logger 类的源码如下:



FileHandler 类的源码如下:



所以我们可以拷贝默认的配置文件到我们工程的 resources 目录下,自定义修改配置信息。

############################################################# 全局属性############################################################
# 顶级RootLogger关联的Handler,多个Handler使用逗号隔开# 对于其他Logger,如果没有指定自己的Handler,则默认继承此handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler
# 默认全局日志级别,Logger和Handler都可以设置自己的日志级别来覆盖此级别.level= ALL############################################################# Handler 配置############################################################
# FileHandler定义# 日志文件存储位置java.util.logging.FileHandler.pattern = ./jul%u.log# 单个文件的最大字节数,0代表不限制java.util.logging.FileHandler.limit = 50000# 文件数量上限,多个文件为jul0.log.0,jul0.log.1 ...java.util.logging.FileHandler.count = 5# 日志级别java.util.logging.FileHandler.level = SEVERE# 日志追加方式java.util.logging.FileHandler.append = true# Handler对象采用的字符集java.util.logging.FileHandler.encoding = UTF-8# 日志格式,使用系统默认的简单格式java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
# ConsoleHandler定义# 日志级别java.util.logging.ConsoleHandler.level = INFO# Handler对象采用的字符集java.util.logging.ConsoleHandler.encoding = UTF-8# 日志格式,使用系统默认的简单格式java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter############################################################# Logger 配置############################################################
# 设置名称为com.chenpi.person的Logger对象的日志级别为WARNINGcom.chenpi.person.level = WARNING
复制代码

然后我们应用中加载我们类路径中自定义的配置文件。

package com.chenpi;
import java.io.IOException;import java.io.InputStream;import java.util.logging.LogManager;import java.util.logging.Logger;import org.junit.Test;
/** * @author 陈皮 * @version 1.0 * @description * @date 2022/3/2 */public class ChenPiJULMain {
// JUL 日志框架演示 @Test public void testLog() throws IOException {
// 读取配置文件 // 也可以通过使用java.util.logging.config.file系统属性指定文件名 // 例如 java -Djava.util.logging.config.file=myfile InputStream resourceAsStream = ChenPiJULMain.class.getClassLoader() .getResourceAsStream("logging.properties"); // 获取LogManager LogManager logManager = LogManager.getLogManager(); // 记载配置文件 logManager.readConfiguration(resourceAsStream);
// 获取日志记录器对象 Logger logger = Logger.getLogger("com.chenpi.ChenPiJULMain");
// 日志记录输出 logger.severe(">>>>> Hello ChenPi!!"); logger.warning(">>>>> Hello ChenPi!!"); logger.info(">>>>> Hello ChenPi!!"); logger.config(">>>>> Hello ChenPi!!"); logger.fine(">>>>> Hello ChenPi!!"); logger.finer(">>>>> Hello ChenPi!!"); logger.finest(">>>>> Hello ChenPi!!");
// 获取日志记录器对象 Logger personLogger = Logger.getLogger("com.chenpi.person");
// 日志记录输出 personLogger.severe(">>>>> Hello Person!!"); personLogger.warning(">>>>> Hello Person!!"); personLogger.info(">>>>> Hello Person!!"); personLogger.config(">>>>> Hello Person!!"); personLogger.fine(">>>>> Hello Person!!"); personLogger.finer(">>>>> Hello Person!!"); personLogger.finest(">>>>> Hello Person!!"); }}
复制代码

控制台和文件中输出内容如下:




自定义 Logger

我们可以针对某一个 Logger 进行单独的配置,例如日志级别,关联的 Handler 等,而不默认继承父级的。


当然我们也可以为以包名为名称的 Logger 进行配置,这样这个包名下的所有子代 Logger 都能继承此配置。

############################################################# 全局属性############################################################
# 顶级RootLogger关联的Handler,多个Handler使用逗号隔开# 对于其他Logger,如果没有指定自己的Handler,则默认继承此handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler
# 默认全局日志级别,Logger和Handler都可以设置自己的日志级别来覆盖此级别.level= ALL############################################################# Handler 配置############################################################
# FileHandler定义# 日志文件存储位置java.util.logging.FileHandler.pattern = ./jul%u.log# 单个文件的最大字节数,0代表不限制java.util.logging.FileHandler.limit = 50000# 文件数量上限java.util.logging.FileHandler.count = 5# 日志级别java.util.logging.FileHandler.level = SEVERE# 日志追加方式java.util.logging.FileHandler.append = true# Handler对象采用的字符集java.util.logging.FileHandler.encoding = UTF-8# 日志格式,使用系统默认的简单格式java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
# ConsoleHandler定义# 日志级别java.util.logging.ConsoleHandler.level = INFO# Handler对象采用的字符集java.util.logging.ConsoleHandler.encoding = UTF-8# 日志格式,使用系统默认的简单格式java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter############################################################# Logger 配置############################################################
# 设置名称为com.chenpi.person的Logger对象的日志级别为WARNINGcom.chenpi.person.level = WARNING# 只关联FileHandlercom.chenpi.person.handlers = java.util.logging.FileHandler# 关闭默认配置com.chenpi.person.useParentHandlers = false
复制代码


package com.chenpi;
import java.io.IOException;import java.io.InputStream;import java.util.logging.LogManager;import java.util.logging.Logger;import org.junit.Test;
/*** @author 陈皮* @version 1.0* @description* @date 2022/3/2*/public class ChenPiJULMain { // JUL 日志框架演示 @Test public void testLog() throws IOException { // 读取配置文件 InputStream resourceAsStream = ChenPiJULMain.class.getClassLoader() .getResourceAsStream("logging.properties"); // 获取LogManager LogManager logManager = LogManager.getLogManager(); // 记载配置文件 logManager.readConfiguration(resourceAsStream); // 获取日志记录器对象 Logger logger = Logger.getLogger("com.chenpi.ChenPiJULMain"); // 日志记录输出 logger.severe(">>>>> Hello ChenPi!!"); logger.warning(">>>>> Hello ChenPi!!"); logger.info(">>>>> Hello ChenPi!!"); logger.config(">>>>> Hello ChenPi!!"); logger.fine(">>>>> Hello ChenPi!!"); logger.finer(">>>>> Hello ChenPi!!"); logger.finest(">>>>> Hello ChenPi!!"); // 获取日志记录器对象 Logger personLogger = Logger.getLogger("com.chenpi.person"); // 日志记录输出 personLogger.severe(">>>>> Hello Person!!"); personLogger.warning(">>>>> Hello Person!!"); personLogger.info(">>>>> Hello Person!!"); personLogger.config(">>>>> Hello Person!!"); personLogger.fine(">>>>> Hello Person!!"); personLogger.finer(">>>>> Hello Person!!"); personLogger.finest(">>>>> Hello Person!!"); }}
复制代码

上述例子中,对于名称为com.chenpi.person的 Logger,只将日志输出到文件中,其他 Logger 则会同时输出到控制台和文件中。



自定义日志格式

SimpleFormatter类源码为例,再到LoggingSupport类源码,发现首先判断我们是否通过java.util.logging.SimpleFormatter.format属性配置了格式,如果没有则使用默认的日志格式。



所以我们可以在配置文件中自定义日志记录的格式。

############################################################# 全局属性############################################################
# 顶级RootLogger关联的Handler,多个Handler使用逗号隔开# 对于其他Logger,如果没有指定自己的Handler,则默认继承此handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler
# 默认全局日志级别,Logger和Handler都可以设置自己的日志级别来覆盖此级别.level= ALL############################################################# Handler 配置############################################################
# FileHandler定义# 日志文件存储位置java.util.logging.FileHandler.pattern = ./jul%u.log# 单个文件的最大字节数,0代表不限制java.util.logging.FileHandler.limit = 1024# 文件数量上限java.util.logging.FileHandler.count = 5# 日志级别java.util.logging.FileHandler.level = FINE# 日志追加方式java.util.logging.FileHandler.append = true# Handler对象采用的字符集java.util.logging.FileHandler.encoding = UTF-8# 日志格式,使用系统默认的简单格式java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter# 自定义SimpleFormatter的日志格式java.util.logging.SimpleFormatter.format = %4$s: %5$s [%1$tc]%n
# ConsoleHandler定义# 日志级别java.util.logging.ConsoleHandler.level = INFO# Handler对象采用的字符集java.util.logging.ConsoleHandler.encoding = UTF-8# 日志格式,使用系统默认的简单格式java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter############################################################# Logger 配置############################################################
# 设置名称为com.chenpi.person的Logger对象的日志级别为WARNINGcom.chenpi.person.level = WARNING
复制代码

这样,所有绑定了 SimpleFormatter 的 Handler 的所有日志记录就使用了自定义的格式。



当然通过源码发现,日志格式类还有其他几个实现,如下所示:



日志过滤器

Filter,日志过滤器,用来对输出的日志记录进行过滤。我们可以根据多个维度进行过滤,例如只输出 message 包含某段文本信息的日志,只输出某个方法中记录的日志,某个级别的日志等等。

Logger 对象将日志信息包装成一个 LogRecord 对象,然后将该对象传给 Handler 进行处理。每一个 Logger 和 Handler 都可以关联一个 Filter。LogRecord 中包含了日志的文本信息、日志生成的时间戳、日志来自于哪个类、日志来自于哪个方法、日志来自于哪个线程等等信息。

Filter 源码如下所示,我们只需要创建一个 Filter 的实现类,重写方法即可。

package java.util.logging;
/*** A Filter can be used to provide fine grain control over* what is logged, beyond the control provided by log levels.* <p>* Each Logger and each Handler can have a filter associated with it.* The Logger or Handler will call the isLoggable method to check* if a given LogRecord should be published. If isLoggable returns* false, the LogRecord will be discarded.** @since 1.4*/@FunctionalInterfacepublic interface Filter { /** * Check if a given log record should be published. * @param record a LogRecord * @return true if the log record should be published. */ public boolean isLoggable(LogRecord record);}
复制代码

我们实现一个 Filter ,如果日志消息包含暴力两个字,则不予放行,即不记录此条日志。

package com.chenpi;
import java.util.logging.Filter;import java.util.logging.LogRecord;
/** * @author 陈皮 * @version 1.0 * @description * @date 2022/3/2 */public class MyLoggerFilter implements Filter {
private static final String SENSITIVE_MESSAGE = "暴力";
@Override public boolean isLoggable(LogRecord record) { String message = record.getMessage(); return null == message || !message.contains(SENSITIVE_MESSAGE); }}
复制代码

然后我们将此过滤器对象设置绑定到 Logger 中即可。

package com.chenpi;
import java.io.IOException;import java.io.InputStream;import java.util.logging.LogManager;import java.util.logging.Logger;import org.junit.Test;
/** * @author 陈皮 * @version 1.0 * @description * @date 2022/3/2 */public class ChenPiJULMain {
// JUL 日志框架演示 @Test public void testLog() throws IOException {
// 读取配置文件 InputStream resourceAsStream = ChenPiJULMain.class.getClassLoader() .getResourceAsStream("logging.properties"); // 获取LogManager LogManager logManager = LogManager.getLogManager(); // 记载配置文件 logManager.readConfiguration(resourceAsStream);
// 获取日志记录器对象 Logger logger = Logger.getLogger("com.chenpi.ChenPiJULMain"); // Logger关联过滤器 logger.setFilter(new MyLoggerFilter());
// 日志记录输出 logger.info(">>>>> Hello ChenPi!!"); logger.info(">>>>> 暴力小孩!!"); }}
// 输出结果如下信息: >>>>> Hello ChenPi!! [星期三 三月 02 15:31:06 CST 2022]
复制代码


点击关注,第一时间了解华为云新鲜技术~

用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
一文详解Java日志框架JUL_Java_华为云开发者社区_InfoQ写作社区