JUL

发布于 2022-11-05  1.69k 次阅读


    Jul全称java.util.Logging,它是java的原生日志框架,使用时不需要另外引用第三方的类库,相对其他的框架更小型灵活,主要使用在小型应用中

1. JUL的组件

JUL的重要组件:

1)Logger:记录器

    应用程序通过获取Logger对象,记录其API来发布日志信息,它是访问日志系统的入口程序

2)Handler:处理器

    每个Logger都会关联一个或一组Handler,Logger会将日志交给关联的Handler去处理,比如:实现日志记录后输出的位置,可以输出到控制台或文件中等等

2)Filter:过滤器

    根据需要定制哪些信息会被记录,哪些信息需要被忽略

3)Formatter:格式化组件

    它负责对日志的数据和信息进行格式化,它决定日志的最终输出形式

4)Level:日志级别

    一组可用于控制日志输出的标准日志记录级别,每条消息都有关联的级别,根据我们输出级别的设置,用来展示最终所呈现的日志信息

JUL的其他组件:

  • Logmanager:有一个全局LogManager对象用于维护关于日志记录器和日志服务的一组共享状态

Handler子组件:

  • ConsoleHandler:将日志输出到控制台,类似于System.err
  • FileHandler:将日志输出到文件
  • SocketHandler:将日志网络传输记录
  • StreamHandler:将日志基于流进行传输记录

Formatter子组件:

  • SimpleFormatter:通用的格式化打印,可读性强
  • XMLFormatter:xml格式打印

2. JUL入门案例

步骤:

  1. 定义日志的入口logger
  2. 输出日志

日志的两种输出方式:

  1. 直接调用日志级别相关的方法, 方法中传递日志输出信息
  2. 调用通用的log方法, 然后头歌Leven指定日志的级别, 传递输出信息

例:

public static void main(String[] args) {
             
             /*
              * 日志程序的入口 java.util.logging.Logger
              * */
             //创建Logger对象,参数为当前类的全路径字符串
             Logger log = Logger.getLogger("logtest.loggingTest");

             //1.直接调用info日志输出方法
             log.info("输出info日志");
             
             //2.指定日志级别输出
             log.log(Level.WARNING,"输出warning日志");
             
       }

输出内容:

十一月 01, 2022 11:42:54 上午 logtest.loggingTest main
信息: 输出info日志
十一月 01, 2022 11:42:54 上午 logtest.loggingTest main
警告: 输出warning日志

3. Level日志级别

3.1 日志级别的说明

 Level类定义了一组可用于控制日志输出的标准日志记录级别。 记录级别对象被排序并且由有序整数指定。 在给定级别启用日志记录还可以在所有更高级别进行日志记录

客户端应通常使用预定义的Level常量,如Level.SEVERE

日志的级别:

  1. SEVERE:错误 (最高级的日志级别)
  2. WARNING:警告
  3. INFO:消息 (默认级别)
  4. CONFIG:配置
  5. FINE:详细信息(少)
  6. FINER:详细信息(中)
  7. FINEST:详细信息(多),最低级的日志级别

两个特殊级别:

  • OFF:可用来关闭日志记录
  • ALL:启用所有消息的日志记录

每一个日志级别都绑定了一个数值:

  • OFF:Integer.MAX_VALUE
  • SEVERE:1000
  • WARNING:900
  • INFO:800
  • CONFIG:700
  • FINE:500
  • FINER:400
  • FINEST:300
  • ALL:Integer.MIN_VALUE

数值的意义在于做判断依据,如果我们设置的日志级别是INFO -- 800那么最终展示的日志信息,必须是数值大于800的所有的日志信息

3.2 默认日志级别展示

演示:默认日志级别演示

public static void main(String[] args) {
             
             Logger log = Logger.getLogger("logtest.DefaultLogLevelPrint");

             log.setLevel(Level.CONFIG);             

             log.severe("输出错误信息!");
             log.warning("输出警告信息!");
             log.info("输出信息!");
             log.config("输出配置信息!");
             log.fine("输出fine信息!");
             log.finer("输出finer信息!");
             log.finest("输出finest信息!");
             
       }

结果:

十一月 01, 2022 6:03:31 下午 logtest.DefaultLogLevelPrint main
严重: 输出错误信息!
十一月 01, 2022 6:03:31 下午 logtest.DefaultLogLevelPrint main
警告: 输出警告信息!
十一月 01, 2022 6:03:31 下午 logtest.DefaultLogLevelPrint main
信息: 输出信息!

仅仅只输出了Info和比Info级别高的日志信息,证明Info是默认的日志级别,比Info低的没有被输出。通过设置日志级别,打印信息也不会改变

日志级别的设置需要和Handler共同使用才能生效

3.3 自定义日志级别

自定义日志级别的步骤:

  • 关闭默认日志打印
  • 定义格式组件
  • 定义处理器,并设置格式化组件
  • 同时设置处理器和logger的日志级别

演示:

public static void main(String[] args) {
             
             //1. 定义日志入口
             Logger log = Logger.getLogger("logtest.MyLogLevelPrint");
             
             //2. 将默认的日志打印方式关闭
             //参数设置为flase,日志打印方式就不会按照父Logger默认的方式进行
             log.setUseParentHandlers(false);
             
             //3. 定义SimpleFormatter格式化组件
             SimpleFormatter simpleformatter = new SimpleFormatter();
             
             //4. 定义ConsoleHandle格式化处理器
             ConsoleHandler consolehandler = new ConsoleHandler();
             
             //5.为处理设置格式化组件
             consolehandler.setFormatter(simpleformatter);
             
             //6. 为Logger设置处理器
             log.addHandler(consolehandler);
             
             
             //7. 设置log和处理器的日志级别
             consolehandler.setLevel(Level.CONFIG);
             log.setLevel(Level.CONFIG);
             
             
             log.severe("输出错误信息!");
             log.warning("输出警告信息!");
             log.info("输出信息!");
             log.config("输出配置信息!");
             log.fine("输出fine信息!");
             log.finer("输出finer信息!");
             log.finest("输出finest信息!");
             
       }

结果:

十一月 01, 2022 7:20:15 下午 logtest.MyLogLevelPrint main
严重: 输出错误信息!
十一月 01, 2022 7:20:15 下午 logtest.MyLogLevelPrint main
警告: 输出警告信息!
十一月 01, 2022 7:20:15 下午 logtest.MyLogLevelPrint main
信息: 输出信息!
十一月 01, 2022 7:20:15 下午 logtest.MyLogLevelPrint main
配置: 输出配置信息!

4. Handler的使用

Handler有四种,但其中使用最多的是ConsoleHandler(日志控制台处理)和FileHandler(日志文件处理)

4.1 将日志输出到文件

日志输出到文件,使用FileHandler处理器

例:

public static void main(String[] args) throws SecurityException,  IOException {
        
        Logger log =Logger.getLogger("logtest.FileHandlerLog");
        log.setUseParentHandlers(false);
        
        //文件处理器
        FileHandler handler = new FileHandler("D:\\filehandler.log");
        
        //格式化输出
        SimpleFormatter simpler = new SimpleFormatter();
        handler.setFormatter(simpler);
        
        //log添加处理器
        log.addHandler(handler);
        
        //设置日志级别
        log.setLevel(Level.ALL);
        handler.setLevel(Level.ALL);
        
        log.severe("输出错误信息!");
        log.warning("输出警告信息!");
        log.info("输出信息!");
        log.config("输出配置信息!");
        log.fine("输出fine信息!");
        log.finer("输出finer信息!");
        log.finest("输出finest信息!");
        
  }

结果:

4.2Logger添加多处理器

    在使用FileHandler时,日志记录被输出到文件中,控制台就无法显示。但Logger可以添加多个处理器,同时输出到文件和控制台

例:Logger同时添加FileHandler和ConsoleHandler

public static void main(String[] args) throws SecurityException, IOException {
             
             Logger log =Logger.getLogger("logtest.FileHandlerLog");
             log.setUseParentHandlers(false);
             
             //文件和控制台处理器
             FileHandler handler = new FileHandler("D:\\filehandler.log");
             ConsoleHandler console = new ConsoleHandler();
             
             //格式化输出
             SimpleFormatter simpler = new SimpleFormatter();
             handler.setFormatter(simpler);
             console.setFormatter(simpler);
             
             //log添加处理器
             log.addHandler(handler);
             log.addHandler(console);
             
             //设置日志级别
             log.setLevel(Level.ALL);
             handler.setLevel(Level.ALL);
             console.setLevel(Level.ALL);
             
             log.severe("输出错误信息!");
             log.warning("输出警告信息!");
             log.info("输出信息!");
             log.config("输出配置信息!");
             log.fine("输出fine信息!");
             log.finer("输出finer信息!");
             log.finest("输出finest信息!");
             
       }

控制台输出:

十一月 01, 2022 9:36:23 下午 logtest.FileHandlerLog main
严重: 输出错误信息!
十一月 01, 2022 9:36:23 下午 logtest.FileHandlerLog main
警告: 输出警告信息!
十一月 01, 2022 9:36:23 下午 logtest.FileHandlerLog main
信息: 输出信息!
十一月 01, 2022 9:36:23 下午 logtest.FileHandlerLog main
配置: 输出配置信息!
十一月 01, 2022 9:36:23 下午 logtest.FileHandlerLog main
详细: 输出fine信息!
十一月 01, 2022 9:36:23 下午 logtest.FileHandlerLog main
较详细: 输出finer信息!
十一月 01, 2022 9:36:23 下午 logtest.FileHandlerLog main
非常详细: 输出finest信息!

5. Logger的父子关系

 在底层存储中Logger是树结构存储,所以Logger存在"父子"关系,每个Logger节点都有自己的父节点和子节点

1)默认节点关系是按包路径来划分的,同样的包,路径越短的节点就为父节点,其他的为子节点

Logger log1 = Logger.getLogger("logtest");
Logger log2 = Logger.getLogger("logtest.ParentsonLogin");
             
System.out.print(log2.getParent() == log1);
输出为:true
2)因为是树存储结构,所有节点都有根节点,这个节点被称为Logger的顶级父类RootLogger
Logger log1 = Logger.getLogger("logtest");
Logger log2 = Logger.getLogger("logtest.ParentsonLogin");

System.out.println("Log1的父类信息:"+log1.getParent()+"\t"+log1.getParent().getName());
System.out.print("Log2的父类信息:"+log2.getParent()+"\t"+log2.getParent().getName());

输出:

Log1的父类信息:java.util.logging.LogManager$RootLogger@33909752   
Log2的父类信息:java.util.logging.Logger@55f96302     logtest

3)父子关系的作用

Logger log1 = Logger.getLogger("logtest");
     Logger log2 = Logger.getLogger("logtest.ParentsonLogin");
     
     log1.setUseParentHandlers(false);
     //log1设置handler和formatter
     ConsoleHandler consol = new ConsoleHandler();
     consol.setFormatter(new SimpleFormatter());
     
     
     //设置log1和的日志级别
     log1.setLevel(Level.ALL);
     consol.setLevel(Level.ALL);
     log1.addHandler(consol);
     
     //对log2子节点进行日志输出
     log2.severe("输出错误信息!");
     log2.warning("输出警告信息!");
     log2.info("输出信息!");
     log2.config("输出配置信息!");
     log2.fine("输出fine信息!");
     log2.finer("输出finer信息!");
     log2.finest("输出finest信息!");

父节点所设置的内容(如handler处理器/格式化组件/日志等级)都可以被子节点继承

输出: 

十一月 02, 2022 11:22:59 上午 logtest.ParentsonLogin main
严重: 输出错误信息!
十一月 02, 2022 11:22:59 上午 logtest.ParentsonLogin main
警告: 输出警告信息!
十一月 02, 2022 11:22:59 上午 logtest.ParentsonLogin main
信息: 输出信息!
十一月 02, 2022 11:22:59 上午 logtest.ParentsonLogin main
配置: 输出配置信息!
十一月 02, 2022 11:22:59 上午 logtest.ParentsonLogin main
详细: 输出fine信息!
十一月 02, 2022 11:22:59 上午 logtest.ParentsonLogin main
较详细: 输出finer信息!
十一月 02, 2022 11:22:59 上午 logtest.ParentsonLogin main
非常详细: 输出finest信息!

log2对象继承了Log1的日志配置信息

6. 配置文件解析

java默认日志配置文件位置:JDK\JDK1.8\jre\lib\logging.properties

#RootLogger的日志级别
#默认情况下,这是全局的日志级别,如果不手动配置其他的日志级别,则默认输出下述配置的级别以及更高的级别
.level= INFO

#默认的处理器
handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler

#文件处理器属性配置
#输出日志文件的路径和文件名
java.util.logging.FileHandler.pattern = %h/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.XMLFormatter

#控制台处理器属性设置
java.util.logging.ConsoleHandler.level = INFO
#设置控制台处理器的格式化
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

#将日志级别设置到具体的某个包下
#com.xyz.foo.level = SEVERE

例:读取自定义的配置文件

① 配置文件(testlog.properties)

.level= INFO

handlers= java.util.logging.FileHandler

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.Level= CONFIG
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter

#追加文件(默认是覆盖)
java.util.logging.FileHandler.append = true

② 读取自定义配置文件

       //创建配置文件流
       InputStream input = new FileInputStream("D:\\testlog.properties");
       
       //取地日志管理器对象
       LogManager logManager = LogManager.getLogManager();
       
       //管理器读取配置文件
       logManager.readConfiguration(input);
       
       //日志入口
       Logger log = Logger.getLogger("logtest.MyReadLogProperties");
       log.setUseParentHandlers(false);
       
       log.severe("输出错误信息!");
       log.warning("输出警告信息!");
       log.info("输出信息!");
       log.config("输出配置信息!");

7. 其他

7.1 日志信息动态传递数据

创建数据类:

public class logbean {
       String logmessage;
       
       int logLeval;
       
       public logbean() {
             
       }
       
       public logbean(String logmessage, int logLeval) {
             super();
             this.logmessage = logmessage;
             this.logLeval = logLeval;
       }
       public String getLogmessage() {
             return logmessage;
       }
       public void setLogmessage(String logmessage) {
             this.logmessage = logmessage;
       }
       public int getLogLeval() {
             return logLeval;
       }
       public void setLogLeval(int logLeval) {
             this.logLeval = logLeval;
       }
       
}

传递动态数据的两种方式:

  • 传统的字符串拼接
  • log提供的动态数据传递

① 字符串拼接

public static void main(String[] args) {
       
             logbean logmes = new logbean("字符串拼接", 1);
             
             Logger log = Logger.getLogger("logtest.logdynamicstatePrint");
             
             log.log(Level.INFO, "消息:"+logmes.getLogmessage()+"\t"+"级别"+logmes.getLogLeval());
       
       }

输出:

十一月 01, 2022 5:20:08 下午 logtest.logdynamicstatePrint main
信息: 消息:字符串拼接      级别1

拼接弊端:

  1. 程序效率低
  2. 可读性不高
  3. 维护成本高

② 动态传递数据

public static void main(String[] args) {
       
             logbean logmes = new logbean("字符串拼接", 1);
             
             Logger log = Logger.getLogger("logtest.logdynamicstatePrint");

             log.log(Level.INFO,"消息:{0} 级别:{1}",new  Object[]{logmes.getLogmessage(),logmes.getLogLeval()});
       }
  • {}:占位符
  • 通过数组中的元素进行占位

输出:

十一月 01, 2022 5:23:57 下午 logtest.logdynamicstatePrint main
信息: 消息:字符串拼接 级别1

JUL的操作流程总结:

  1. 初始化LogManager,LogManager加载Logging.properies配置文件
  2. 从单例的LogManager获取Logger
  3. 设置日志级别Level,在打印的过程中使用到日志记录的LogRecord类
  4. Filter作为过滤器提供了日志级别之外细粒度的控制
  5. Handler日志处理器,决定日志的输出位置或者控制台等等
  6. Formatter用来格式化输出

路漫漫其修远兮,吾将上下而求索