JUL日志框架
1、JUL 简单介绍
JUL全称Java util logging是Java原生的日志框架,使用时不需要另外引用第三方类库,相对其他日志框 架使用方便,学习简单,能够在小型应用中灵活使用。
在JUL中有以下组件:
- Loggers:被称为记录器,应用程序通过获取Logger对象,调用其API来来发布日志信息。Logger通常时应用程序访问日志系统的入口程序
- Handlers:也被称为Appenders,每个Logger都会关联一组Handlers,Logger会将日志交给关联的Handler处理,由Handler负责将日志做记录。Handlers在此是一个抽象,其具体的实现决定了日志记录的位置可以是控制台、文件、网络上的其他日志服务或操作系统日志等
- Formatters:也被称为Layouts,它负责对日志事件中的数据进行转换和格式化。它决定了数据在一条日志记录中的最终形式
- Level:日志的输出级别,每条日志消息都有一个关联的级别。我们根据输出级别的设置,来展现最终所呈现的日志信息,根据不同的需求设置不同级别
- Filters:过滤器,根据需要定制哪些信息会被记录,哪些信息会被放过
总结一下就是:
用户使用Logger来进行日志记录,Logger持有若干个Handler,日志的输出操作是由Handler完成的。 在Handler在输出日志前,会经过Filter的过滤,判断哪些日志级别过滤放行哪些拦截,Handler会将日志内容输出到指定位置(日志文件、控制台等)。Handler在输出日志时会使用Layout,将输出内容进行排版。
JUC 流程示意图:
┌──────────────────────────────────────────────────────────────────────────────────────┐
│ LogManager─┐ Level Filter Formatter │
│ ⬇ ⬇ ⬇ ⬇ │
│ Application-------->Logger-------->LogRecord-------->Handler-------->Outside World │
└──────────────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────────────┐
│ 应用程序 运行 日志记录器 记录日志 日志处理器 处理日志 │
│ Application-------->Logger---------------->Handler------------------>Outside World │
│ ⬇ ⬇ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │Filters(日志过滤器), Formatters(日志格式化组件), Level日志输出级别│ │
│ └──────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────────────┘
JUC 总体流程:
- 初始化LogManager,LogManager加载logging.properties配置,添加Logger到LogManager中
- 从单例LogManager获取Logger
- 设置级别Level,并指定日志记录LogRecord
- Filter提供了日志级别之外更细粒度的控制
- Handler是用来处理日志输出位置
- Formatter是用来格式化LogRecord的
JUL 日志原理解析:
- Logger:是用来记录日志的,负责调用其有的Handler进行日志输出
- LogRecord:是用来传递日志的,一条具体的日志对象,比如这条日志所在线程、日志级别,日志名称,日志内容,时间等信息
- Handler:是用来导出(处理)日志的,负责具体的日志输出,有不同的实现类,比如(ConsoleHandler 控制台输出,FileHandler 文件输出)
- Level:是用来定义日志级别的(控制日志输出)
- Filter:提供了日志级别之外更细粒度的控制
- Formatter:是用来格式化LogRecord的,Handler在输出前会调用Formatter 对LogRecord 进行格式化,如简单字符串格式化,Html格式化,XML格式化等
- 此外还有一个单一的全局的LogManager维护Logger和管理日志(LogManager 负责创建日志对象,并将创建的日志对象继承RootLogger的默认配置)。JUL主要由这七个核心类或接口组成,再有就是一些子类或者实现类。API中关于这几个类或接口的类和方法的详细介绍这里不再复述。现在我们已经对JUL有了一个整体的了解,并且对核心类或接口的功能职责也有了初步了解,接下就该结合代码看看JUL的真面目。
在任何日志框架中都会有继承性的概念,在获取Logger对象时,默认会继承RootLogger的配置,但自己的Logger也可以单独设置级别,Handler等。但是如果日志名称是包名进行命名的话,子包默认会继承父包的配置,例如 指定了com.a.b这个包的Handler是ConsoleHandler,Level是info,则com.a.b.c或者 com.a.b.c.d等属于这个包的Logger都会默认继承这个包的配置
2、JUL 入门案例
import java.util.logging.Level;
import java.util.logging.Logger;
public class Test {
//1、获取日志记录器对象
public static Logger logger = Logger.getLogger(Test.class.getName());
public static void main(String[] args) {
/**
* 对于日志输出有两种方式
* 1.一种是指定日志级别方法输出(支持占位符输出)
* 2.另一种是通用方法输出,需要参数指定日志级别(不支持占位符输出)
*/
// 1.通用方法输出,需要参数指定日志级别
logger.log(Level.SEVERE, "this is a severe");
logger.log(Level.WARNING, "this is a warn");
logger.log(Level.INFO, "this is a info"); // JUL默认日志级别,比info高的输出,比info低的过滤掉
logger.log(Level.CONFIG, "this is a config");
logger.log(Level.FINE, "this is a fine");
logger.log(Level.FINER, "this is a finer");
logger.log(Level.FINEST, "this is a finest");
logger.log(Level.INFO, "this is a info", new Exception("My Exception")); // 带异常输出
// 2.指定日志级别方法输出,有不同的级别,可带参数,其它类似
logger.severe("severe log");
logger.warning("warning log");
logger.info("info log"); // JUL默认日志级别,比info高的输出,比info低的过滤掉
logger.config("config log");
logger.fine("fine log");
logger.finer("finer log");
logger.finest("finest log");
// 3.占位符输出, 只有.log()方法是支持占位符输出的
logger.log(Level.WARNING, "this is a warn, {0} ", "p1");
logger.log(Level.INFO, "this is a info, {0} {1}", new Object[]{"p1","p2"});
}
}
// 输出内容
3月 26, 2022 11:21:58 上午 Test main
严重: this is a severe
3月 26, 2022 11:21:58 上午 Test main
警告: this is a warn
3月 26, 2022 11:21:58 上午 Test main
信息: this is a info
3月 26, 2022 11:21:58 上午 Test main
信息: this is a info
java.lang.Exception: My Exception
at Test.main(Test.java:21)
3月 26, 2022 11:21:58 上午 Test main
严重: severe log
3月 26, 2022 11:21:58 上午 Test main
警告: warning log
3月 26, 2022 11:21:58 上午 Test main
信息: info log
3月 26, 2022 11:21:58 上午 Test main
警告: this is a warn, p1
3月 26, 2022 11:21:58 上午 Test main
信息: this is a info, p1 p2
因为默认的日志级别是INFO,所以比INFO级别低的 CONFIG、FINE、FINER、FINEST的日志记录消息没有记录。
3、JUL 组件介绍
JUL常用组件如下:
- Level:日志级别
- Handler:日志输出地
- Formatter:日志格式化
1、Level(日志级别)
日志级别由高到低:OFF/SEVERE/WARNIN/INFO/CONFIG/FINE/FINERG/FINEST/ALL,每个级别有自己的数值,在java.util.logging.Level类中源码如下:
public static final Level OFF = new Level("OFF",Integer.MAX_VALUE, defaultBundle);
public static final Level SEVERE = new Level("SEVERE",1000, defaultBundle);
public static final Level WARNING = new Level("WARNING", 900, defaultBundle);
public static final Level INFO = new Level("INFO", 800, defaultBundle);
public static final Level CONFIG = new Level("CONFIG", 700, defaultBundle);
public static final Level FINE = new Level("FINE", 500, defaultBundle);
public static final Level FINER = new Level("FINER", 400, defaultBundle);
public static final Level FINEST = new Level("FINEST", 300, defaultBundle);
public static final Level ALL = new Level("ALL", Integer.MIN_VALUE, defaultBundle);
- Level.SEVERE:最高级别的日志,主要记录错误信息
- Level.WARNING:级别排行第二,记录警告信息
- Level.INFO:级别排行第三,最常用的日志级别,记录普通消息,比如请求信息、连接信息、参数信息等等
- Level.CONFIG:级别排行第四,记录配置信息,加载配置文件、读取配置参数都可以使用CONFIG记录
- Level.FINE :debug 时记录的 日志消息,记录运行时的状态、传递的参数等等
- Level.FINER:debug 时记录的 日志消息,记录运行时的状态、传递的参数等等
- Level.FINEST:debug 时记录的 日志消息,记录运行时的状态、传递的参数等等
- Level.OFF: 关闭日志记录
- Level.ALL:所有记录都开启
可自定义日志级别:继承java.util.logging.Level类即可
2、Handler(日志输出)
日志输出的目的,java.util.logging包中有如下四种(ConsoleHandler、FileHandler、SocketHandler、MemoryHandler):
- StreamHandler(父类):
- ConsoleHandler:输出到控制台,默认Level为INFO,流为System.err,Formatter为SimpleFormatter
- FileHandler:输出到文件,默认Level为INFO,Formatter为XMLFormatter
- SocketHandler:输出到socket
- MemoryHandler:输出到内存
可自定义日志输出:继承java.util.logging.Handler类即可
3、Formatter(日志格式化)
定义日志输出的格式,目前有SimpleFormatter和XMLFormatter两种格式,分别对应简单格式和xml格式。
注意:默认情况下:ConsoleHandler对应SimpleFormatter,FileHandler对应XMLFormatter。
可自定输出格式:继承抽象类java.util.logging.Formatter即可
4、自定义日志输出
1、自定义输出日志级别(控制台)
Java默认的只能输出3个级别,并且默认的还是输出到控制台。我们现在想要输出其他多个级别,只能自定义输出级别了。自定义的时候,先要关闭默认的。
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
public class Test {
public static void main(String[] args) {
// 1.获取日志记录器对象
Logger logger = Logger.getLogger(Test.class.getName());
// 2.关闭系统默认配置(需要自定义日志配置必须关闭)
logger.setUseParentHandlers(false);
// 3.自定义配置日志级别
// 创建ConsoleHandler控制台输出
ConsoleHandler consoleHandler = new ConsoleHandler();
// 创建简单格式转换对象
SimpleFormatter simpleFormatter = new SimpleFormatter();
// 进行关联
consoleHandler.setFormatter(simpleFormatter);
logger.addHandler(consoleHandler);
// 配置日志具体级别,注意:Logger与Handler都需要配置
logger.setLevel(Level.ALL);
consoleHandler.setLevel(Level.ALL);
// 4.日志记录输出,可以发现设置级别为全部输出后,可以在控制台看到所有输出了
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
}
}
// 输出内容
3月 26, 2022 11:56:15 上午 Test main
严重: severe
3月 26, 2022 11:56:15 上午 Test main
警告: warning
3月 26, 2022 11:56:15 上午 Test main
信息: info
3月 26, 2022 11:56:15 上午 Test main
配置: config
3月 26, 2022 11:56:15 上午 Test main
详细: fine
3月 26, 2022 11:56:15 上午 Test main
较详细: finer
3月 26, 2022 11:56:15 上午 Test main
非常详细: finest
2、自定义日志输出到文件(磁盘)
import java.io.IOException;
import java.util.logging.*;
public class Test {
public static void main(String[] args) throws IOException {
// 1.获取日志记录器对象
Logger logger = Logger.getLogger(Test.class.getName());
// 2.关闭系统默认配置(需要自定义日志配置必须关闭)
logger.setUseParentHandlers(false);
// 自定义配置日志Level、Handler、Formatter
/**
* 创建ConsoleHandler控制台输出
* 并且设置为SimpleFormatter简单格式转换对象(默认就是SimpleFormatter)
* 日志输出级别为ALL全部级别
*/
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setFormatter(new SimpleFormatter());
logger.addHandler(consoleHandler);
// 配置Handler日志具体级别
consoleHandler.setLevel(Level.ALL);
/**
* 创建FileHandler文件输出
* 并且设置为SimpleFormatter简单格式转换对象(默认是XMLFormatter)
* 日志输出级别为ALL全部级别
*/
// 第二个参数是日志输出内容追加,注意:目录必须提前存在,否则会抛异常
FileHandler fileHandler = new FileHandler("./logs/jul.log", true);
fileHandler.setFormatter(new SimpleFormatter());
logger.addHandler(fileHandler);
// 配置Handler日志具体级别
fileHandler.setLevel(Level.ALL);
// 配置Logger日志输出级别
logger.setLevel(Level.ALL);
// 3.日志记录输出,以下级别从大到小
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
}
}
// 输出内容(文件磁盘中也是同样的输出)
3月 26, 2022 12:27:32 下午 Test main
严重: severe
3月 26, 2022 12:27:32 下午 Test main
警告: warning
3月 26, 2022 12:27:32 下午 Test main
信息: info
3月 26, 2022 12:27:32 下午 Test main
配置: config
3月 26, 2022 12:27:32 下午 Test main
详细: fine
3月 26, 2022 12:27:33 下午 Test main
较详细: finer
3月 26, 2022 12:27:33 下午 Test main
非常详细: finest
以上创建了两个Handler,一个是控制台的,一个是文件的,把这个Handler设置到日志记录器对象里面。以后的日志不仅可以输出控制台,还可以输出到文件
可以把文件输出Formatter改成默认的XMLFormatter,观察文件内容
FileHandler fileHandler = new FileHandler("./logs/jul.log", true);
fileHandler.setFormatter(new XMLFormatter()); // 这行代码可写可不写,因为FileHandler默认就是XMLFormatter
输出到文件的内容:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
<date>2022-03-26T04:33:02.178157900Z</date>
<millis>1648269182178</millis>
<nanos>157900</nanos>
<sequence>0</sequence>
<logger>Test</logger>
<level>SEVERE</level>
<class>Test</class>
<method>main</method>
<thread>1</thread>
<message>severe</message>
</record>
<record>
<date>2022-03-26T04:33:02.228169900Z</date>
<millis>1648269182228</millis>
<nanos>169900</nanos>
<sequence>1</sequence>
<logger>Test</logger>
<level>WARNING</level>
<class>Test</class>
<method>main</method>
<thread>1</thread>
<message>warning</message>
</record>
<record>
<date>2022-03-26T04:33:02.229171100Z</date>
<millis>1648269182229</millis>
<nanos>171100</nanos>
<sequence>2</sequence>
<logger>Test</logger>
<level>INFO</level>
<class>Test</class>
<method>main</method>
<thread>1</thread>
<message>info</message>
</record>
<record>
<date>2022-03-26T04:33:02.232172300Z</date>
<millis>1648269182232</millis>
<nanos>172300</nanos>
<sequence>3</sequence>
<logger>Test</logger>
<level>CONFIG</level>
<class>Test</class>
<method>main</method>
<thread>1</thread>
<message>config</message>
</record>
<record>
<date>2022-03-26T04:33:02.233171500Z</date>
<millis>1648269182233</millis>
<nanos>171500</nanos>
<sequence>4</sequence>
<logger>Test</logger>
<level>FINE</level>
<class>Test</class>
<method>main</method>
<thread>1</thread>
<message>fine</message>
</record>
<record>
<date>2022-03-26T04:33:02.233171500Z</date>
<millis>1648269182233</millis>
<nanos>171500</nanos>
<sequence>5</sequence>
<logger>Test</logger>
<level>FINER</level>
<class>Test</class>
<method>main</method>
<thread>1</thread>
<message>finer</message>
</record>
<record>
<date>2022-03-26T04:33:02.234172500Z</date>
<millis>1648269182234</millis>
<nanos>172500</nanos>
<sequence>6</sequence>
<logger>Test</logger>
<level>FINEST</level>
<class>Test</class>
<method>main</method>
<thread>1</thread>
<message>finest</message>
</record>
</log>
5、Logger之间的父子关系
JUL中Logger之间存在父子关系,这种父子关系通过树状结构存储,JUL在初始化时会创建一个顶层RootLogger作为所有Logger父Logger,存储上作为树状结构的根节点。并父子关系通过路径来关联。默认子Logger会继承父Logger的属性。
import java.io.IOException;
import java.util.logging.*;
public class Test {
public static void main(String[] args) throws IOException {
// 1.获取日志记录器对象, logger1 是 logger2 的父对象
Logger logger1 = Logger.getLogger("com");
Logger logger2 = Logger.getLogger("com.controller");
// 2.测试
System.out.println(logger1 == logger2.getParent()); // true
// 所有日志记录器的顶级父元素 LogManager$RootLogger,name 为 ""
System.out.println("logger1 Parent:"+logger1.getParent() + " ,name:" + logger1.getParent().getName());
// 打印logger2的父类logger1的对象与名称
System.out.println("logger2 Parent:"+logger2.getParent() + " ,name:" + logger2.getParent().getName());
// 3.logger1父类:关闭默认配置, 并自定义配置日志级别和设置ConsoleHandler控制台输出
logger1.setUseParentHandlers(false);
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setFormatter(new SimpleFormatter());
consoleHandler.setEncoding("UTF-8");
consoleHandler.setLevel(Level.ALL);
logger1.addHandler(consoleHandler);
logger1.setLevel(Level.ALL);
// 4.使用logger2子类输出(查看是否继承父类的属性)
logger2.severe("severe");
logger2.warning("warning");
logger2.info("info");
logger2.config("config");
logger2.fine("fine");
logger2.finer("finer");
logger2.finest("finest");
}
}
// 输出内容
true
logger1 Parent:java.util.logging.LogManager$RootLogger@4b9e13df ,name:
logger2 Parent:java.util.logging.Logger@2b98378d ,name:com
3月 26, 2022 12:57:31 下午 Test main
严重: severe
3月 26, 2022 12:57:31 下午 Test main
警告: warning
3月 26, 2022 12:57:31 下午 Test main
信息: info
3月 26, 2022 12:57:31 下午 Test main
配置: config
3月 26, 2022 12:57:31 下午 Test main
详细: fine
3月 26, 2022 12:57:31 下午 Test main
较详细: finer
3月 26, 2022 12:57:31 下午 Test main
非常详细: finest
从执行结果可以看出子类默认继承了父类Logger的配置。
注意:如果子类不想或者不需要继承父类Logger的配置,只需要关闭子类Logger默认配置,然后重新自定配置即可。
import java.io.IOException;
import java.util.logging.*;
public class Test {
public static void main(String[] args) throws IOException {
// 1.获取日志记录器对象, logger1 是 logger2 的父对象
Logger logger1 = Logger.getLogger("com");
Logger logger2 = Logger.getLogger("com.controller");
// 2.logger1父类:关闭默认配置, 并自定义配置日志级别和设置ConsoleHandler控制台输出
logger1.setUseParentHandlers(false);
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setFormatter(new SimpleFormatter());
consoleHandler.setEncoding("UTF-8");
consoleHandler.setLevel(Level.ALL);
logger1.addHandler(consoleHandler);
logger1.setLevel(Level.ALL);
// 3.使用logger2子类输出(查看是否继承父类的属性)
System.out.println("===================如下是继承父类配置的输出==========================");
log(logger2);
// 4.重写自定义logger2的日志配置(不想使用logger1父类继承的配置)
// 注意: 如果没有取消默认配置,那么当前又设置了子类的属性,logs输出会输出2次重复的,因为继承父类输出1次,子类自己输出1次
logger2.setUseParentHandlers(false);
ConsoleHandler consoleHandler2 = new ConsoleHandler();
consoleHandler.setLevel(Level.WARNING);
logger2.addHandler(consoleHandler2);
logger2.setLevel(Level.WARNING);
// 4.使用logger2子类输出(查看是否继承父类的属性)
System.out.println("===================如下是没有继承父类配置的输出==========================");
log(logger2);
}
private static void log(Logger logger2) {
logger2.severe("severe");
logger2.warning("warning");
logger2.info("info");
logger2.config("config");
logger2.fine("fine");
logger2.finer("finer");
logger2.finest("finest");
}
}
// 输出内容
===================如下是继承父类配置的输出==========================
3月 26, 2022 1:06:48 下午 Test log
严重: severe
3月 26, 2022 1:06:48 下午 Test log
警告: warning
3月 26, 2022 1:06:48 下午 Test log
信息: info
3月 26, 2022 1:06:48 下午 Test log
配置: config
3月 26, 2022 1:06:48 下午 Test log
详细: fine
3月 26, 2022 1:06:48 下午 Test log
较详细: finer
3月 26, 2022 1:06:48 下午 Test log
非常详细: finest
===================如下是没有继承父类配置的输出==========================
3月 26, 2022 1:06:48 下午 Test log
严重: severe
3月 26, 2022 1:06:48 下午 Test log
警告: warning
6、JUL自定义配置文件
以上所有配置相关操作都是以Java硬编码的形式进行的的,我们可以使用更加清晰,更加专业的一种做法,就是使用配置文件,如果我们没有自己添加配置文件,则会使用系统(JDK)默认的配置文件。默认配置文件路径$JAVAHOME\jre\lib\logging.properties
,默认配置文件内容如下
############################################################
# Default Logging Configuration File
#
# You can use a different file by specifying a filename
# with the java.util.logging.config.file system property.
# For example java -Djava.util.logging.config.file=myfile
############################################################
############################################################
# Global properties
############################################################
# "handlers" specifies a comma separated list of log Handler
# classes. These handlers will be installed during VM startup.
# Note that these classes must be on the system classpath.
# By default we only configure a ConsoleHandler, which will only
# show messages at the INFO and above levels.
handlers= java.util.logging.ConsoleHandler
# To also add the FileHandler, use the following line instead.
#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler
# Default global logging level.
# This specifies which kinds of events are logged across
# all loggers. For any given facility this global level
# can be overriden by a facility specific level
# Note that the ConsoleHandler also has a separate level
# setting to limit messages printed to the console.
.level= INFO
############################################################
# Handler specific properties.
# Describes specific configuration info for Handlers.
############################################################
# default file output is in user's home directory.
java.util.logging.FileHandler.pattern = %h/java%u.log
java.util.logging.FileHandler.limit = 50000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
# Limit the message that are printed on the console to INFO and above.
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# Example to customize the SimpleFormatter output format
# to print one-line log message like this:
# <level>: <log message> [<date/time>]
#
# java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n
############################################################
# Facility specific properties.
# Provides extra control for each logger.
############################################################
# For example, set the com.xyz.foo logger to only log SEVERE
# messages:
com.xyz.foo.level = SEVERE
自定义配置文件:logging.properties。将该文件保存至resources目录下
# RootLogger顶级父元素指定的默认处理器,多个用逗号分隔
# handlers = java.util.logging.FileHandler,java.util.logging.ConsoleHandler
handlers = java.util.logging.ConsoleHandler
# RootLogger顶级父元素默认的日志级别为:ALL
.level = ALL
# 文件处理器属性设置
# 输出日志文件的路径
java.util.logging.FileHandler.pattern = ./logs/java%u.log
# 输出日志文件的限制(50000字节)
java.util.logging.FileHandler.limit = 50000
# 设置日志文件的数量
java.util.logging.FileHandler.count = 1
# 设置输出日志的格式,默认是以XML方式输出的
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
# 指定handler对象的日志级别,默认是INFO级别
java.util.logging.FileHandler.level = ALL
# 指定义追加的方式添加日志内容
java.util.logging.FileHandler.append = true
# 控制台处理器属性设置
# 控制台默认输出的格式
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# 指定handler对象的字符集编码方式
java.util.logging.ConsoleHandler.encoding = UTF-8
# 控制台输出默认的级别设置(默认是INFO)
java.util.logging.ConsoleHandler.level = ALL
# 指定日志消息格式
#设置日志格式
# java.util.logging.SimpleFormatter.format= %1$tc %2$s%n%4$s: %5$s%6$s%n
java.util.logging.SimpleFormatter.format = %4$s: %5$s [%1$tc]%n
# 自定义Logger使用:包名 + handlers/level
com.xyz.handlers = java.util.logging.ConsoleHandler,java.util.logging.FileHandler
com.xyz.level = SEVERE
# 必须关闭默认设置(只有关闭该配置,自定义设置才能生效,否则还会使用RootLogger的属性配置)
com.xyz.useParentHandlers = false
加载自定义配置文件代码:
package com.xyz;
import java.io.InputStream;
import java.util.logging.LogManager;
import java.util.logging.Logger;
public class Test {
public static void main(String[] args) throws Exception {
// 读取配置文件,通过类加载器
InputStream ins = Test.class.getClassLoader().getResourceAsStream("logging.properties");
// 创建LogManager
LogManager logManager = LogManager.getLogManager();
// 通过LogManager加载配置文件
logManager.readConfiguration(ins);
// 创建日志记录器(当前Logger使用的配置是RootLogger的)
Logger logger = Logger.getLogger("com");
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
// 创建日志记录器(当前Logger使用的是自己自动配置的, Level配置为severe)
Logger logger2 = Logger.getLogger("com.xyz");
logger2.severe("severe test");
logger2.warning("warning test");
logger2.info("info test");
logger2.config("config test");
logger2.fine("fine test");
logger2.finer("finer test");
logger2.finest("finest test");
}
}
// 输出内容
严重: severe [周六 3月 26 14:57:57 CST 2022]
警告: warning [周六 3月 26 14:57:58 CST 2022]
信息: info [周六 3月 26 14:57:58 CST 2022]
配置: config [周六 3月 26 14:57:58 CST 2022]
详细: fine [周六 3月 26 14:57:58 CST 2022]
较详细: finer [周六 3月 26 14:57:58 CST 2022]
非常详细: finest [周六 3月 26 14:57:58 CST 2022]
严重: severe test [周六 3月 26 14:57:58 CST 2022]
7、JUL自定义组件方式
这里通过继承Level、Handler、Formatter自定义Logger,日志存在文件中,且每个不同的loggerName存不同日志文件中。示例如下,解释请查看注释:
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.logging.*;
public class Test {
/**
* 自定义日志level
*/
public static class SelfLevel extends Level {
public static SelfLevel ERROR = new SelfLevel("ERROR", 910);
/**
* @param name 自定义级别名称
* @param value 自定义级别的权重值
*/
protected SelfLevel(String name, int value) {
super(name, value);
}
}
/**
* 自定义日志格式formatter
*/
public static class SelfFormatter extends Formatter {
@Override
public String format(LogRecord record) {
return String.format("[%s] %s %s.%s: %s\r\n",
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
record.getLevel().getName(),
record.getSourceClassName(),
record.getSourceMethodName(),
record.getMessage());
}
}
/**
* 自定义日志输出handler
*/
public static class SelfHandle extends Handler {
/**
* 日志写入的位置
*/
@Override
public void publish(LogRecord record) {
try {
// 每一个不同的loggerName分别记在不同的日志文件中
File file = new File(String.format("%s_%s.log", record.getLoggerName(),
LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))));
if (!file.exists()) {
file.createNewFile();
}
FileWriter fw = new FileWriter(file, true);
PrintWriter pw = new PrintWriter(fw);
String log = String.format("[%s] %s %s.%s: %s\r\n",
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
record.getLevel().getName(),
record.getSourceClassName(),
record.getSourceMethodName(),
record.getMessage());
pw.write(log);
pw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 刷新缓存区,保存数据
*/
@Override
public void flush() {
}
/**
* 关闭
*/
@Override
public void close() throws SecurityException {
}
}
public static void main(String[] args) {
// 创建日志记录器
Logger logger = Logger.getLogger(Test.class.getName());
// 生成日志输出handler实例
logger.addHandler(new SelfHandle());
logger.info("info log");
logger.severe("severe log");
//使用自定义的logger level
logger.log(SelfLevel.ERROR, "error log");
System.out.println("------------------------");
Logger log = Logger.getLogger("com.test");
log.setUseParentHandlers(false);
ConsoleHandler console = new ConsoleHandler();
console.setFormatter(new SelfFormatter());
log.addHandler(console);
log.log(Level.INFO, "selfFormatter test");
}
}
控制台输出内容如下:
3月 27, 2022 6:19:11 下午 Test main
信息: info log
3月 27, 2022 6:19:11 下午 Test main
严重: severe log
3月 27, 2022 6:19:11 下午 Test main
ERROR: error log
------------------------
[2022-03-27 18:19:11] INFO Test.main: selfFormatter test
在项目同级目录会生产文件Test_2022-03-27.log
[2022-03-27 18:19:11] INFO Test.main: info log
[2022-03-27 18:19:11] SEVERE Test.main: severe log
[2022-03-27 18:19:11] ERROR Test.main: error log
8、Logger private static final 为啥?
为什么将 Logger 对象声明为 private static final:
- private:是为了防止其他类使用当前类的日志对象
- static:是为了让每个类中的日志对象只生成一份,是属于类的,不是属于具体的实例的
- final:是为了避免日志对象在运行时被修改
9、参考文献 & 鸣谢
- java日志:一、JUL使用【CSDN:小徐也要努力鸭】https://blog.csdn.net/a232884c/article/details/121103424
- 日志技术之JUL入门【CSDN:StudyWinter】https://blog.csdn.net/Zhouzi_heng/article/details/107943477
- java日志(二)–java官方日志jul的使用【CSDN:panda-star】https://blog.csdn.net/chinabestchina/article/details/104730196