Blog

Make it work, make it right, make it fast

Jan 3, 2022 - 3 minute read - 编程

Java日志框架-JUL

一、简介

JUL(java.util.logging)是Java原生日志框架,使用时不需要引入第三方类库。

二、相关组件

  • Logger:应用程序进行日志记录调用的主要实体。 Logger 对象用于记录特定系统或应用程序组件的消息。
  • LogRecord:用于在日志框架和单个日志处理程序之间传递日志请求的实体类。
  • Handler:将 LogRecord 对象导出到各种目的地,包括内存、输出流、控制台、文件和套接字。
  • Level:定义一组可用于控制日志输出的标准日志级别。程序可以配置为输出某些级别的日志,而忽略其他级别的输出。
  • Formatter:提供对格式化 LogRecord 对象的支持。这个包包括两个格式化程序,SimpleFormatter 和 XMLFormatter,分别用于格式化纯文本或 XML 格式的日志记录。

三、简单使用

JUL记录日志的时候,可以选择不同的级别以区分不同的日志信息,JUL主要有如下7个级别(此外,还有一个级别 OFF 可用于关闭日志记录,还有一个级别 ALL 可用于启用所有日志记录)

  • SEVERE (highest value):错误
  • WARNING:警告
  • INFO(default value):消息
  • CONFIG:配置
  • FINE:详细信息(少)
  • FINER:详细信息(中)
  • FINEST (lowest value):详细信息(多)

接下来,使用Logger分别打印7个级别日志。

package com.example;

import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import org.junit.Test;

public class AppTest {

    @Test
    public void testLog() {
        Logger logger = Logger.getLogger(this.getClass().getName());

        //打印不同Level日志
        logger.severe("it's a severe level log");
        logger.warning("it's a warning level log");
        logger.info("it's a info level log");
        logger.config("it's a config level log");
        logger.fine("it's a fine level log");
        logger.finer("it's a finer level log");
        logger.finest("it's a finest level log");
        
    }
}

日志输出

从控制台输出可以看到,我们打印了7个不同级别的日志,最终只输出了severe、warning、info三个级别的日志,这是因为系统默认的日志级别是info,接下来使用logger.setLevel(Level.CONFIG);设置输出CONFIG级别及该级别以上的日志,观察输出。

设置CONFIG日志输出

可以发现输出并没有任何变化,这是因为logger需要添加自定义的handler。

package com.example;

import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import org.junit.Test;

public class AppTest {

    @Test
    public void testLog() {
        Logger logger = Logger.getLogger(this.getClass().getName());

        //不使用父级处理器
        logger.setUseParentHandlers(false);
        
        
        ConsoleHandler handler = new ConsoleHandler();
        SimpleFormatter formatter = new SimpleFormatter();
    
        handler.setFormatter(formatter);
        logger.addHandler(handler);
         
        logger.setLevel(Level.ALL);
        handler.setLevel(Level.ALL);

        logger.severe("it's a severe level log");
        logger.warning("it's a warning level log");
        logger.info("it's a info level log");
        logger.config("it's a config level log");
        logger.fine("it's a fine level log");
        logger.finer("it's a finer level log");
        logger.finest("it's a finest level log");
        
        
    }
}

日志输出示例

将日志输出到文件,这时候就无需使用ConsoleHandler,而是使用FileHandler。

package com.example;

import java.io.IOException;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import org.junit.Test;

/**
 * Unit test for simple App.
 */
public class AppTest {

    @Test
    public void testFileHandlerLog() throws SecurityException, IOException {
        Logger logger = Logger.getLogger(this.getClass().getName());

        logger.setUseParentHandlers(false);
        

        FileHandler fileHandler = new FileHandler("/Users/wag/projects/demo/log.log");

        SimpleFormatter formatter = new SimpleFormatter();

        fileHandler.setFormatter(formatter);
        logger.addHandler(fileHandler);
         
        logger.setLevel(Level.ALL);
        fileHandler.setLevel(Level.ALL);

        logger.severe("it's a severe level log");
        logger.warning("it's a warning level log");
        logger.info("it's a info level log");
        logger.config("it's a config level log");
        logger.fine("it's a fine level log");
        logger.finer("it's a finer level log");
        logger.finest("it's a finest level log");
        
        
    }
}

日志输出示例

此时,如果还需要添加控制台输出,则直接将上面的ConsoleHandler添加到logger的处理器中即可。

JUL的父子关系

JUL的Logger存在着父子关系,该父子关系不是一般意义上的继承,而是通过树结构存在的,比如:

package com.example;

import java.io.IOException;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import org.junit.Test;

/**
 * Unit test for simple App.
 */
public class AppTest {

    @Test
    public void logParentTest() {
        Logger parentLogger = Logger.getLogger("com.example");
        Logger subLogger = Logger.getLogger("com.example.app");

        System.out.println(subLogger.getParent() == parentLogger); //true
        System.out.println(subLogger.getParent().getName()); //com.example
        System.out.println(parentLogger.getParent()); //java.util.logging.LogManager$RootLogger@31cefde0
//RootLogger是所有Logger对象的顶层Logger
    }
}

接下来对parentLogger做相关设置,然后使用subLogger进行打印。

package com.example;

import java.io.IOException;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import org.junit.Test;

/**
 * Unit test for simple App.
 */
public class AppTest {

    @Test
    public void logParentTest() {
        Logger parentLogger = Logger.getLogger("com.example");
        Logger subLogger = Logger.getLogger("com.example.app");

        parentLogger.setUseParentHandlers(false);
        ConsoleHandler handler = new ConsoleHandler();
        SimpleFormatter formatter = new SimpleFormatter();

        handler.setFormatter(formatter);
        parentLogger.addHandler(handler);
         
        parentLogger.setLevel(Level.CONFIG);
        handler.setLevel(Level.CONFIG);

        subLogger.severe("it's a severe level log");
        subLogger.warning("it's a warning level log");
        subLogger.info("it's a info level log");
        subLogger.config("it's a config level log");
        subLogger.fine("it's a fine level log");
        subLogger.finer("it's a finer level log");
        subLogger.finest("it's a finest level log");
    }
}

日志输出示例

系统默认配置文件

JUL一般都是以文件的方式进行配置,如果没有设置配置文件,则会读取系统默认配置文件,位置为jre/lib/logging.properties

#Logger使用的处理器,如果需要多个可以在后面直接添加,以逗号分隔
handlers= java.util.logging.ConsoleHandler

.level= INFO

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

java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

自定义配置文件输出级别

接着在src下新建配置文件,并将INFO改为CONFIG,然后读取配置文件观察输出。

package com.example;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import org.junit.Test;

/**
 * Unit test for simple App.
 */
public class AppTest {

    
    @Test
    public void fileConfigTest() throws SecurityException, IOException{
        InputStream in = new FileInputStream("/Users/wag/projects/demo/logging.properties");

                             
        LogManager logManager = LogManager.getLogManager();
        logManager.readConfiguration(in);
        Logger logger = Logger.getLogger(this.getClass().getName());
        
        logger.severe("it's a severe level log");
        logger.warning("it's a warning level log");
        logger.info("it's a info level log");
        logger.config("it's a config level log");
        logger.fine("it's a fine level log");
        logger.finer("it's a finer level log");
        logger.finest("it's a finest level log");
    }
}

日志输出示例

可以看到在配置文件中自定义的输出级别生效了。

自定义配置文件日志

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.SimpleFormatter

#自定义Logger
com.example.AppTest.handlers = java.util.logging.FileHandler
com.example.AppTest.level = CONFIG
com.example.AppTest.useParentHandlers = false

运行后,在用户目录下可以看到java0.log,并且设置了useParentHandlers=false,控制台不会有任何输出。

日志输出示例