简介
- 本篇将介绍如何使用
JDK
原生自带的日志框架JUL
。
JUL日志框架
JUL
全称Java Util Logging
,是Java
原生的日志框架,使用时不需要另外引入第三方类库,相对于其他日志框架来说其特点是使用方便,能够在小型应用中灵活应用。JUL
日志框架使用的频率并不高,但一旦需要解除此类的代码,仍然要求开发人员能够迅速看懂代码,并理解。
框架结构
- 结构图:
Loggers
:被称为记录器,应用程序通过获取Logger
对象,调用其API
来发布日志信息,Logger
通常是应用程序访问日志系统的入口程序;Appenders
:也被称为Handlers
,每个Logger
都会关联一组Handlers
,Logger
会将日志交给关联的Handlers
处理,由Handlers
负责将日志记录;Handlers
在此是一个抽象类,由其具体的实现决定日志记录的位置是控制台、文件、网络上的其他日志服务异或是操作系统日志;Layouts
:也被称为Formatters
,它负责对日志事件中的数据进行转换和格式化,Layouts
决定了记录的数据在一条日志记录中的最终显示形式;Level
:每条日志消息都有一个关联的日志级别。该级别粗略指导了日志消息的重要性和紧迫,可以将Level
和Loggers
或Appenders
做关联以便于我们过滤消息;Filters
:过滤器,根据需要定制哪些信息会被记录,哪些信息会被放过。- 一个完整的日志记录过程如下:
- 用户使用
Logger
来进行日志记录的行为,Logger
可以同时持有若干个Handler
,日志输出操作是由Handler
完成的; - 在
Handler
输出日志前,会经过Filter
的过滤,判断哪些日志级别放行、哪些拦截,Handler
会将日志内容输出到指定位置(日志文件、控制台等); Handler
在输出日志时会使用Layout
对日志内容进行排版,之后再输出到指定的位置。
- 用户使用
入门案例
- 示例代码:
@Testpublic void testQuick() { // 1.创建JUL Logger对象无法传入class对象,迂回战术可以获取该类之后获取名字 Logger logger = Logger.getLogger(JulTest.class.getName()); // 2.输出日志的两种方式,其中日志级别共有7种,还有2种特殊级别 logger.info("Hello, here is Java Util Logging"); logger.log(Level.INFO, "You can also use this way to output log."); // 3.尽量不采用拼接字符串的形式,采用占位符的形式,占位符中需要填写索引编号 String name = "dylan"; int age = 12; logger.log(Level.INFO, "[USER]:{0} [AGE]:{1}", new Object[]{name, age});}
日志级别
JUL
内置工7
种日志级别,另外有2
中特殊级别:java.util.logging.Level
中定义了7
种日志级别:SEVERE
(最高等级)WARNING
INFO
(默认等级)CONFIG
FINE
FINER
FINEST
(最低等级)
- 还有
2
种特殊日志级别,用于开关日志:OFF
:可用于关闭日志记录;ALL
:启用所有消息的日志记录。
- 示例代码:
- 默认的日志级别是由
RootLogger
决定的,所有的Logger
对象默认都会继承并使用RootLogger
所提供的控制台输出处理器对象ConsoleHandler
; - 同时,
RootLogger
的默认日志输出等级为INFO
,则所有未经配置的Logger
默认也是使用该日志级别。
- 默认的日志级别是由
/** * JUL默认的日志级别是Info,所有级别高于等于Info的日志都会被输出控制台。*/@Testpublic void testLogLevel() { // 1.获取日志记录器对象,其中getLogger的参数name是此日志对象Logger的名称,可以由logger.getName()取出 final Logger logger = Logger.getLogger("cn.dylanphang.jul.JulTest"); // 2.使用默认的ConsoleHandler输出各级别的日志,默认情况下只有Server、Warning和Info会输出到控制台 logger.severe("Level Severe."); logger.warning("Level Warning."); logger.info("Level Info."); logger.config("Level Config."); logger.fine("Level Fine."); logger.finer("Level Finer."); logger.finest("Level Finest");}
- 运行输出:
- 尽管代码中定义了输出
INFO
等级以下的日志,但实际控制台中并没有相关的日志信息,这是因为此时创建的Logger
对象继承并使用了RootLogger
中的日志等级和处理器对象。 - 考虑以下代码:
@Testpublic void testFindDefault() { final Logger logger = Logger.getLogger("cn.dylanphang.jul.JulTest"); System.out.println("Default Logger's Level: " + logger.getLevel()); System.out.println("Default Logger's Handlers' Quantity: " + logger.getHandlers().length);}
- 其中采用更为直观的方式打印当前
Logger
对象的日志等级,和与其关联的处理器对象数量。运行输出,得到:
- 这并不意外,默认情况下由于
Logger
继承并使用RootLogger
中的日志等级与处理器对象,因此对于它自身来说,并不拥有任何的日志等级信息与处理器对象。 - 考虑以下代码:
@Testpublic void testParentLogger() { final Logger logger = Logger.getLogger("cn.dylanphang.jul.JulTest"); final Logger loggerParent = logger.getParent(); System.out.println("Logger's Default Parent Logger is: " + loggerParent.getClass().getSimpleName()); System.out.println("Parent Logger's Level: " + loggerParent.getLevel()); System.out.println("Parent Logger's Handlers' Quantity: " + loggerParent.getHandlers().length); for (Handler handler : loggerParent.getHandlers()) { System.out.println("Default " + handler.getClass().getSimpleName() + "'s Level: " + handler.getLevel()); }}
- 同样采用更直观的方式打印当前
Logger
的父日志相关信息,得到以下输出:
- 至此,不难推断出
Logger
的日志输出等级取决于RootLogger
。 - 还有一个定论,即
Logger
和Handler
的日志等级是相互牵制的,其中等级较高一方的配置生效。 - 通过更改
RootLogger
中的日志级别及其ConsoleHandler
的日志级别,编写以下代码:
@Testpublic void testLevel() { // 0.准备工作 final Logger logger = Logger.getLogger("cn.dylanphang.jul.JulTest"); final Logger parent = logger.getParent(); // 1.RootLogger日志等级高于ConsoleHandler日志等级 parent.setLevel(Level.WARNING); parent.getHandlers()[0].setLevel(Level.CONFIG); logger.severe("[SEVERE ]Something."); logger.warning("[WARNING]Something."); logger.info("[INFO ]Something."); logger.config("[CONFIG ]Something."); System.out.println("============================================="); // 2.RootLogger日志等级小于ConsoleHandler日志等级 parent.setLevel(Level.FINER); logger.severe("[SEVERE ]Something."); logger.warning("[WARNING]Something."); logger.info("[INFO ]Something."); logger.config("[CONFIG ]Something."); logger.fine("[FINE ]Something."); logger.finer("[FINER ]Something.");}
- 运行输出:
- 程序的输出与此前的定论一致。
自定义配置
- 通常会在单独的配置文件中去配置
Logger
的日志等级和处理器类型等,但作为入门,需要了解如何在Java
代码中,通过更改Logger
日志级别和配置自定义ConsoleHandler
的方式,去影响日志输出。 - 如果不希望
Logger
对象使用RootLogger
中的日志级别进行输出,则需要对Logger
进行以下配置:- 重新设置
Logger
的日志输出等级; - 重新配置
Logger
的处理器Handler
类型,并不再使用RootLogger
中提供的默认处理器。
- 重新设置
- 测试类
testUserDefined
源码如下:
@Testpublic void testUserDefined() { // 1.获取日志记录器对象 Logger logger = Logger.getLogger("cn.dylanphang.jul.JulTest"); // 2.配置Logger使其不再继承使用RootLogger中的所有Handler logger.setUseParentHandlers(false); // 3.自定义ConsoleHandler对象,并配置该处理器的日志等级 ConsoleHandler consoleHandler = new ConsoleHandler(); consoleHandler.setLevel(Level.ALL); // 4.为Logger添加自定义的ConsoleHandler logger.addHandler(consoleHandler); // 5.由于Logger默认会使用RootLogger的日志等级,如果希望输出Level.ALL的日志,同时需要设置Logger的日志等级也为Level.ALL // *.否则,Logger将会使用RootLogger的默认日志等级INFO,最终日志只会输出等级高于或等于INFO的内容 logger.setLevel(Level.ALL); // 6.分别输出各等级的日志 logger.severe("[SEVERE ]Something."); logger.warning("[WARNING]Something."); logger.info("[INFO ]Something."); logger.config("[CONFIG ]Something."); logger.fine("[FINE ]Something."); logger.finer("[FINER ]Something."); logger.finest("[FINEST ]Something.");}
- 运行输出:
- 而开发中较为经常的使用方式,是通过
logging.properties
文件的方式进行配置:- 程序中需要编写代码去显示加载
logging.properties
文件以启用自定义的配置; - 配置文件中,
Handler
是单独进行配置的,开发人员可以单独定义控制台输出日志的处理器对象ConsoleHandler
或文件输出日志的处理器对象FileHandler
等; - 具备相关自定义
Handler
后,需要将Logger
与Handler
进行关联,配置文件支持RootLogger
或指定名称的Logger
与自定义Handler
进行关联; - 无论任何时候,都需要明确日志最终的输出等级,是同时由
Logger
与其相关联的Handler
所决定的。
- 程序中需要编写代码去显示加载
# RootLogger的日志级别(默认INFO),所有的Handler都受限于此日志级别,Handler的日志级别可以比RootLogger的日志级别高.level=ALL# RootLogger默认的处理器,可以配置多个,所有非手动解除父日志的子日志都将使用这些处理器handlers=java.util.logging.ConsoleHandler, java.util.logging.FileHandler# ConsoleHandler控制台输出处理器配置# 指定ConsoleHandler默认日志级别java.util.logging.ConsoleHandler.level=ALLjava.util.logging.ConsoleHandler.encoding=UTF-8# FileHandler文件输出处理器配置# 指定FileHandler默认日志级别java.util.logging.FileHandler.level=INFO# 日志文件输出路径java.util.logging.FileHandler.pattern=/dylan%u.log# 单个日志文件大小,单位是bit,1024bit即为1kbjava.util.logging.FileHandler.limit=1024*1024*10# 日志文件数量,如果数量为2,则会生成dylan.log.0文件和dylan.log.1文件,总容量为: (limit * count)bitjava.util.logging.FileHandler.count=1# FileHandler持有的最大并发锁数java.util.logging.FileHandler.maxLocks=100# 指定要使用的Formatter类的名称,FileHandler默认使用的是
- 测试类
test()
的加载过程如下:- 项目是通过
Maven
构建的,默认使用getResourceAsStream(String fileName)
将读取resource
目录下的配置文件; - 其中
LogManager
对象是用于全局配置日志的管理对象,它是单例的,使用它来加载应用配置文件; - 配置文件使用
readConfiguration(InputStream is)
加载应用后,RootLogger
的日志级别和与之关联的Handler
及其日志级别就已经配置完毕,与logging.properties
中一致; - 同时特殊名称
cn.hanna
的Logger
会遵循配置文件中设置的日志等级INFO
,其关闭依赖RootLogger
,同时仅仅将日志输出到控制台中。
- 项目是通过
@Testpublic void testUserDefined() throws IOException { // 1.读取配置文件 final InputStream is = Jul2Test.class.getClassLoader().getResourceAsStream("logging.properties"); // 2.获取LogManager,LogManager是单例对象,并加载应用配置文件logging.properties final LogManager logManager = LogManager.getLogManager(); logManager.readConfiguration(is); // 4.正常输出日志 final Logger loggerNormal = Logger.getLogger(this.getClass().getName()); loggerNormal.severe("[SEVERE ]Something."); loggerNormal.warning("[WARNING]Something."); loggerNormal.info("[INFO ]Something."); loggerNormal.config("[CONFIG ]Something."); loggerNormal.fine("[FINE ]Something."); loggerNormal.finer("[FINER ]Something."); loggerNormal.finest("[FINEST ]Something."); System.out.println("============================================="); // 5.指定日志对象的名称,配置文件中对cn.hanna名称的Logger进行了特殊配置 final Logger loggerSpecial = Logger.getLogger("cn.hanna"); loggerSpecial.severe("[SEVERE ]Something."); loggerSpecial.warning("[WARNING]Something."); loggerSpecial.info("[INFO ]Something."); loggerSpecial.config("[CONFIG ]Something."); loggerSpecial.fine("[FINE ]Something."); loggerSpecial.finer("[FINER ]Something."); loggerSpecial.finest("[FINEST ]Something.");}
- 运行测试,控制台输出:
- 日志文件
dylan0.log
输出:
占位符相关
- 留意到配置内
formatter
中的包含了相关占位符,如果希望掌握这一部分的含义,需要首先了解String.format()
的使用。 - 考虑以下代码:
@Testpublic void test() { String name = "Mike"; double score = 89; Calendar calendar = Calendar.getInstance(); calendar.set(1992, Calendar.JANUARY, 3); final String formatA = String.format("[%1$-5s] %2$tF score: (%3$-8.2f)", name, calendar, score); final String formatB = String.format("[%1$-5s] %2$tY-%2$tm-%2$td score: (%3$8.2f)", name, calendar, score); System.out.println(formatA); System.out.println(formatB);}
- 运行输出:
- 可以看到,
String.format
可以将数据按照指定的格式转换为String
类型的数据,其中需要遵循以下规则:%[argument_index$][flags][width][.precision]conversion
- 参数说明:
参数 | 描述 |
---|---|
argument_index$ | 格式化参数在String.format 中的索引位置,从1 开始计算索引 |
flags | 关于此格式化数据的额外格式设定,如左对齐- 、是否显示正负数符号+ |
width | 此格式数据所占用的宽度。当宽度大于参数的实际宽度时,会自动将格式化数据右对齐 |
.precision | 需要格式化的是一个浮点数时,可以自定义该浮点数格式化后所需保留的小数点位数 |
conversion | 表示需要格式的数据类型是什么 |
- 关于
String.format
的详细内容,可以参考以下网址,其中提供了大量的关于各种类型的各种格式化占位符信息:- https://docs.oracle.com/javase/7/docs/api/java/util/Formatter.html
- 了解
String.format
后,需要知道其实日志记录的格式化,内部使用的也是String.format
,形式如下:String.format(format, date, source, logger, level, message, thrown);
- 参数说明:
参数 | 描述 |
---|---|
format | SimpleFormatter.format 中使用的格式,也是配置文件logging.properties 中定义的格式 |
date | 日志的输出日期 |
source | 日志的调用者,如果不存在则会输出日志对象的名称 |
logger | 日志对象的名称 |
level | 日志等级 |
message | 日志信息 |
thrown | 当有异常时,会在日志信息中包含异常信息,如果不存在则不输出 |
- 关于
SimpleFormatter
的详细内容,可以参考以下网址,其中包含了更详细的说明与示例:- https://docs.oracle.com/javase/7/docs/api/java/util/logging/SimpleFormatter.html
- 初步了解相关原理后,重新审视配置文件中的
%4$s: %5$s [%1$tc]%n
:%4$s
:索引位4
是level
,表示日志等级,数据类型是String
,使用的conversion
为s
;%5$s
:索引位5
是message
,表示日志信息,数据类型是String
,使用的conversion
为s
;%1$tc
:索引位1
是date
,表示日志输出日期,数据类型是Date
,使用的conversion
为tc
;%n
:换行符。
- 因此,当运行代码
logger.severe("[SEVERE ]Something.");
,将得到日志记录,对比自定义格式:
- 格式与日志输出格式一致。
滤器配置
- 关于
Filter
过滤器,了解即可。 - 代码中需要使用
Logger
对象中的setFilter
方法,配置一个Filter
对象,其源码如下:
@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);}
- 其中真正控制日志是否运行记录的对象是
LogRecord
,使用其中的getMessage()
可以获取到日志信息。 - 当方法
isLoggable
返回false
时,不记录日志;返回true
则记录日志。 - 测试代码如下:
- 其中使用
lambda
表达式,返回的结果为!record.getMessage().contains("What")
,即如果日志记录包含了关键字What
,则返回false
,表示过滤该条日志信息,不进行记录操作。
- 其中使用
@Testpublic void test() throws IOException { final InputStream is = Jul3Test.class.getClassLoader().getResourceAsStream("logging.properties"); final LogManager logManager = LogManager.getLogManager(); logManager.readConfiguration(is); final Logger logger = Logger.getLogger(this.getClass().getName()); logger.severe("[SEVERE ]Something What."); logger.warning("[WARNING]Something."); logger.info("[INFO ]Something."); logger.config("[CONFIG ]Something."); logger.fine("[FINE ]Something What."); logger.finer("[FINER ]Something."); logger.finest("[FINEST ]Something What."); System.out.println("============================================="); // *.配置过滤器Filter logger.setFilter((x) -> !x.getMessage().contains("What")); logger.severe("[SEVERE ]Something What."); logger.warning("[WARNING]Something."); logger.info("[INFO ]Something."); logger.config("[CONFIG ]Something."); logger.fine("[FINE ]Something What."); logger.finer("[FINER ]Something."); logger.finest("[FINEST ]Something What.");}
- 运行输出:
Filter
生效并成功将包含What
的日志记录过滤。
完整的配置文件示例
- 最后贴一个
JUL
可用的配置文件示例,其中RootLogger
仅使用了ConsoleHandler
输出日志:
.level=ALLhandlers=java.util.logging.ConsoleHandlerjava.util.logging.ConsoleHandler.level=ALLjava.util.logging.ConsoleHandler.encoding=UTF-8java.util.logging.FileHandler.level=INFOjava.util.logging.FileHandler.pattern=/sample%u.logjava.util.logging.FileHandler.limit=1024*1024*10java.util.logging.FileHandler.count=1java.util.logging.FileHandler.maxLocks=100java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatterjava.util.logging.FileHandler.encoding=UTF-8java.util.logging.FileHandler.append=truejava.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%ncn.xyz.handlers=java.util.logging.ConsoleHandler, java.util.logging.FileHandlercn.xyz.level=INFOcn.xyz.useParentHandlers=false
- 关于其他的
Handler
不作演示了,因为实际遇到的情况很少,有需要的时候查阅相关官方资料即可。
总结
- 关于
JUL
的介绍到此为止,总体来说是一款很轻量的框架; - 在项目体量很小时,可以使用
JUL
记录相关日志,免去使用其他日志门面或框架的繁琐操作,如依赖导入等。
原文转载:http://www.shaoqun.com/a/504554.html
woot:https://www.ikjzd.com/w/604
夸克:https://www.ikjzd.com/w/1237
轻量的日志框架JUL的使用介绍。简介本篇将介绍如何使用JDK原生自带的日志框架JUL。JUL日志框架JUL全称JavaUtilLogging,是Java原生的日志框架,使用时不需要另外引入第三方类库,相对于其他日志框架来说其特点是使用方便,能够在小型应用中灵活应用。JUL日志框架使用的频率并不高,但一旦需要解除此类的代码,仍然要求开发人员能够迅速看懂代码,并理解。框架结构结构图:Loggers:被
亿恩网:亿恩网
自贸区跨境通网站:自贸区跨境通网站
马尔代夫好玩吗?:马尔代夫好玩吗?
阳江闸坡冰雪王国需要带什么去?阳江冰雪王国可以滑雪吗?:阳江闸坡冰雪王国需要带什么去?阳江冰雪王国可以滑雪吗?
马尔代夫有什么礼仪禁忌?:马尔代夫有什么礼仪禁忌?
没有评论:
发表评论