2020年12月30日星期三

JUL日志框架

轻量的日志框架JUL的使用介绍。

简介

  • 本篇将介绍如何使用JDK原生自带的日志框架JUL

JUL日志框架

  • JUL全称Java Util Logging,是Java原生的日志框架,使用时不需要另外引入第三方类库,相对于其他日志框架来说其特点是使用方便,能够在小型应用中灵活应用。
  • JUL日志框架使用的频率并不高,但一旦需要解除此类的代码,仍然要求开发人员能够迅速看懂代码,并理解。

框架结构

  • 结构图:

  • Loggers:被称为记录器,应用程序通过获取Logger对象,调用其API来发布日志信息,Logger通常是应用程序访问日志系统的入口程序;
  • Appenders:也被称为Handlers,每个Logger都会关联一组HandlersLogger会将日志交给关联的Handlers处理,由Handlers负责将日志记录;Handlers在此是一个抽象类,由其具体的实现决定日志记录的位置是控制台、文件、网络上的其他日志服务异或是操作系统日志;
  • Layouts:也被称为Formatters,它负责对日志事件中的数据进行转换和格式化,Layouts决定了记录的数据在一条日志记录中的最终显示形式;
  • Level:每条日志消息都有一个关联的日志级别。该级别粗略指导了日志消息的重要性和紧迫,可以将LevelLoggersAppenders做关联以便于我们过滤消息;
  • Filters:过滤器,根据需要定制哪些信息会被记录,哪些信息会被放过。
  • 一个完整的日志记录过程如下:
    1. 用户使用Logger来进行日志记录的行为,Logger可以同时持有若干个Handler,日志输出操作是由Handler完成的;
    2. Handler输出日志前,会经过Filter的过滤,判断哪些日志级别放行、哪些拦截,Handler会将日志内容输出到指定位置(日志文件、控制台等);
    3. 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种日志级别:
    1. SEVERE(最高等级)
    2. WARNING
    3. INFO(默认等级)
    4. CONFIG
    5. FINE
    6. FINER
    7. FINEST(最低等级)
  • 还有2种特殊日志级别,用于开关日志:
    1. OFF:可用于关闭日志记录;
    2. ALL:启用所有消息的日志记录。
  • 示例代码:
    1. 默认的日志级别是由RootLogger决定的,所有的Logger对象默认都会继承并使用RootLogger所提供的控制台输出处理器对象ConsoleHandler
    2. 同时,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
  • 还有一个定论,即LoggerHandler的日志等级是相互牵制的,其中等级较高一方的配置生效。
  • 通过更改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进行以下配置:
    1. 重新设置Logger的日志输出等级;
    2. 重新配置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文件的方式进行配置:
    1. 程序中需要编写代码去显示加载logging.properties文件以启用自定义的配置;
    2. 配置文件中,Handler是单独进行配置的,开发人员可以单独定义控制台输出日志的处理器对象ConsoleHandler或文件输出日志的处理器对象FileHandler等;
    3. 具备相关自定义Handler后,需要将LoggerHandler进行关联,配置文件支持RootLogger或指定名称的Logger与自定义Handler进行关联;
    4. 无论任何时候,都需要明确日志最终的输出等级,是同时由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()的加载过程如下:
    1. 项目是通过Maven构建的,默认使用getResourceAsStream(String fileName)将读取resource目录下的配置文件;
    2. 其中LogManager对象是用于全局配置日志的管理对象,它是单例的,使用它来加载应用配置文件;
    3. 配置文件使用readConfiguration(InputStream is)加载应用后,RootLogger的日志级别和与之关联的Handler及其日志级别就已经配置完毕,与logging.properties中一致;
    4. 同时特殊名称cn.hannaLogger会遵循配置文件中设置的日志等级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);
  • 参数说明:
参数描述
formatSimpleFormatter.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
    1. %4$s:索引位4level,表示日志等级,数据类型是String,使用的conversions
    2. %5$s:索引位5message,表示日志信息,数据类型是String,使用的conversions
    3. %1$tc:索引位1date,表示日志输出日期,数据类型是Date,使用的conversiontc
    4. %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

跨境电商:https://www.ikjzd.com/

woot:https://www.ikjzd.com/w/604

夸克:https://www.ikjzd.com/w/1237


轻量的日志框架JUL的使用介绍。简介本篇将介绍如何使用JDK原生自带的日志框架JUL。JUL日志框架JUL全称JavaUtilLogging,是Java原生的日志框架,使用时不需要另外引入第三方类库,相对于其他日志框架来说其特点是使用方便,能够在小型应用中灵活应用。JUL日志框架使用的频率并不高,但一旦需要解除此类的代码,仍然要求开发人员能够迅速看懂代码,并理解。框架结构结构图:Loggers:被
亿恩网:亿恩网
自贸区跨境通网站:自贸区跨境通网站
马尔代夫好玩吗?:马尔代夫好玩吗?
阳江闸坡冰雪王国需要带什么去?阳江冰雪王国可以滑雪吗?:阳江闸坡冰雪王国需要带什么去?阳江冰雪王国可以滑雪吗?
马尔代夫有什么礼仪禁忌?:马尔代夫有什么礼仪禁忌?

没有评论:

发表评论