A detailed explanation of the Java logging framework JUL

Abstract: JUL (Java util logging), Java native logging framework, does not require the introduction of third-party dependencies, and is simple and convenient to use.

This article is shared from HUAWEI CLOUD Community " Java Log Framework JUL Detailed Explanation Daquan ", author: Chen Pi's JavaLib.

Introduction to JUL

JUL (Java util logging), a Java native logging framework, does not require the introduction of third-party dependencies, and is simple and convenient to use. It is generally used in small applications, and is rarely used in mainstream projects.

JUL Architecture

  • Application: Java application.
  • Logger: A logger, a Java application publishes log records by calling the logger's methods.
  • Handler: Handler, each Logger can be associated with one or more Handlers, Handler obtains logs from Logger and outputs the logs to a certain destination, the destination can be console, local file, or network log service, or send them Forward to OS logs and so on. A Handler can be disabled through the Handler.setLevel(level.off) method, or other levels can be set to enable this Handler.
  • Filter: Filter, which log records to filter based on conditions. Each Logger and Handler can be associated with a Filter.
  • Formatter : A formatter responsible for transforming and formatting log records in log events.
  • Level: Each log record has an associated log level, indicating the importance and urgency of the log. It is also possible to set the associated log level for Logger and Handler.

Getting started example

package com.chenpi;

import java.util.logging.Level;
import java.util.logging.Logger;
import org.junit.Test;

/**
 * @author 陈皮
 * @version 1.0
 * @description
 * @date 2022/3/2
 */
public class ChenPiJULMain {

  // JUL 日志框架演示
  @Test
  public void testLog() {

    // 获取日志记录器对象
    Logger logger = Logger.getLogger("com.chenpi.ChenPiJULMain");

    // 日志记录输出
    logger.severe(">>>>> Hello ChenPi!!");
    logger.warning(">>>>> Hello ChenPi!!");
    logger.info(">>>>> Hello ChenPi!!"); // 默认日志级别
    logger.config(">>>>> Hello ChenPi!!");
    logger.fine(">>>>> Hello ChenPi!!");
    logger.finer(">>>>> Hello ChenPi!!");
    logger.finest(">>>>> Hello ChenPi!!");

    // 输出指定日志级别的日志记录
    logger.log(Level.WARNING, ">>>>> Hello Warning ChenPi!!");

    // 占位符形式
    String name = "ChenPi";
    int age = 18;
    logger.log(Level.INFO, ">>>>> Hello {0},{1} years old!", new Object[]{name, age});

    // 异常堆栈信息
    logger.log(Level.SEVERE, ">>>>> Hello NPE!", new NullPointerException());

  }
}

JUL outputs log information to the console by default, and the default log level is info. The console output is as follows:

Logger parent-child inheritance relationship

In JUL, Logger has the concept of parent-child inheritance relationship, and the parent-child inheritance relationship is divided according to the inclusion relationship of the name of the Logger object. For a Logger object, if the explicitly created parent Logger object cannot be found, then its parent Logger is the root Logger, that is, RootLogger.

The child Logger object will inherit the configuration of the parent Logger, such as log level, associated Handler, log format and so on. For any Logger object, if it is not specially configured, it will eventually inherit the configuration of RootLogger.

package com.chenpi;

import java.util.logging.Logger;
import org.junit.Test;

/**
 * @author 陈皮
 * @version 1.0
 * @description
 * @date 2022/3/2
 */
public class ChenPiJULMain {

  @Test
  public void testLog() {

    // logger1的父Logger是RootLogger
    Logger logger1 = Logger.getLogger("com.chenpi.a");
    // logger2的父Logger是logger1  
    Logger logger2 = Logger.getLogger("com.chenpi.a.b.c");

    Logger logger1Parent = logger1.getParent();
    System.out.println("logger1Parent:" + logger1Parent + ",name:" + logger1Parent.getName());

    Logger logger2Parent = logger2.getParent();
    System.out.println("logger1:" + logger1 + ",name:" + logger1.getName());
    System.out.println("logger2Parent:" + logger2Parent + ",name:" + logger2Parent.getName());

  }
}

// 输出结果如下
logger1Parent:java.util.logging.LogManager$RootLogger@61e717c2,name:
logger1:java.util.logging.Logger@66cd51c3,name:com.chenpi.a
logger2Parent:java.util.logging.Logger@66cd51c3,name:com.chenpi.a

log configuration

We can adjust JUL's default logging behavior (set log level, log output destination, log format, etc.) in 2 ways, one is by hardcoding the form in the program (not recommended), the other is by separate Configuration file form.

Hardcoded logging configuration

In JUL, Logger has a parent-child inheritance relationship, so when we need to configure a Logger object separately, we need to set it to not inherit the configuration of the parent Logger.

The following demonstrates that the Logger object named com.chenpi.ChenPiJULMain is configured separately and associated with two Handlers.

package com.chenpi;

import java.io.IOException;
import java.util.logging.*;
import org.junit.Test;

/**
 * @author 陈皮
 * @version 1.0
 * @description
 * @date 2022/3/2
 */
public class ChenPiJULMain {

  @Test
  public void testLog() throws IOException {

    // 获取日志记录器对象
    Logger logger = Logger.getLogger("com.chenpi.ChenPiJULMain");

    // 关闭默认配置,即不使用父Logger的Handlers
    logger.setUseParentHandlers(false);

    // 设置记录器的日志级别为ALL
    logger.setLevel(Level.ALL);

    // 日志记录格式,使用简单格式转换对象
    SimpleFormatter simpleFormatter = new SimpleFormatter();

    // 控制台输出Handler,并且设置日志级别为INFO,日志记录格式
    ConsoleHandler consoleHandler = new ConsoleHandler();
    consoleHandler.setLevel(Level.INFO);
    consoleHandler.setFormatter(simpleFormatter);

    // 文件输出Handler,并且设置日志级别为FINE,日志记录格式
    FileHandler fileHandler = new FileHandler("./jul.log");
    fileHandler.setLevel(Level.FINE);
    fileHandler.setFormatter(simpleFormatter);

    // 记录器关联处理器,即此logger对象的日志信息输出到这两个Handler进行处理
    logger.addHandler(consoleHandler);
    logger.addHandler(fileHandler);

    // 日志记录输出
    logger.severe(">>>>> Hello ChenPi!!");
    logger.warning(">>>>> Hello ChenPi!!");
    logger.info(">>>>> Hello ChenPi!!");
    logger.config(">>>>> Hello ChenPi!!");
    logger.fine(">>>>> Hello ChenPi!!");
    logger.finer(">>>>> Hello ChenPi!!");
    logger.finest(">>>>> Hello ChenPi!!");
  }
}

Because the log level set by the Logger is ALL, that is, all levels of log records can pass. But the log level set by ConsoleHandler is INFO, so the console only outputs log records above the INFO level. The log level set by FileHandler is FINE, so the log records above the FINE level are output in the log file.

Here is the log output from the console:

The following are the log results output in the log file jul.log:

log configuration file

Through debug debugging, according to the following method sequence, it can be found that if we do not configure the JUL configuration file, the system reads the default configuration file logging.properties from the lib directory under the JDK installation directory by default.

getLogger()  -> demandLogger() -> LogManager.getLogManager() -> ensureLogManagerInitialized() -> owner.readPrimordialConfiguration() -> readConfiguration()

The following is the content of the JUL default log configuration file that comes with the JDK:

For the configuration options, you can actually find out through the source code of the corresponding class. For example, the source code of the Logger class is as follows:

The source code of the FileHandler class is as follows:

So we can copy the default configuration file to the resources directory of our project and customize the configuration information.

############################################################
# 全局属性
############################################################

# 顶级RootLogger关联的Handler,多个Handler使用逗号隔开
# 对于其他Logger,如果没有指定自己的Handler,则默认继承此
handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler

# 默认全局日志级别,Logger和Handler都可以设置自己的日志级别来覆盖此级别
.level= ALL
############################################################
# Handler 配置
############################################################

# FileHandler定义
# 日志文件存储位置
java.util.logging.FileHandler.pattern = ./jul%u.log
# 单个文件的最大字节数,0代表不限制
java.util.logging.FileHandler.limit = 50000
# 文件数量上限,多个文件为jul0.log.0,jul0.log.1 ...
java.util.logging.FileHandler.count = 5
# 日志级别
java.util.logging.FileHandler.level = SEVERE
# 日志追加方式
java.util.logging.FileHandler.append = true
# Handler对象采用的字符集
java.util.logging.FileHandler.encoding = UTF-8
# 日志格式,使用系统默认的简单格式
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter

# ConsoleHandler定义
# 日志级别
java.util.logging.ConsoleHandler.level = INFO
# Handler对象采用的字符集
java.util.logging.ConsoleHandler.encoding = UTF-8
# 日志格式,使用系统默认的简单格式
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
############################################################
# Logger 配置
############################################################

# 设置名称为com.chenpi.person的Logger对象的日志级别为WARNING
com.chenpi.person.level = WARNING

Then we load the custom configuration file in our classpath in our application.

package com.chenpi;

import java.io.IOException;
import java.io.InputStream;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import org.junit.Test;

/**
 * @author 陈皮
 * @version 1.0
 * @description
 * @date 2022/3/2
 */
public class ChenPiJULMain {

  // JUL 日志框架演示
  @Test
  public void testLog() throws IOException {

    // 读取配置文件
    // 也可以通过使用java.util.logging.config.file系统属性指定文件名
    // 例如 java -Djava.util.logging.config.file=myfile
    InputStream resourceAsStream = ChenPiJULMain.class.getClassLoader()
        .getResourceAsStream("logging.properties");
    // 获取LogManager
    LogManager logManager = LogManager.getLogManager();
    // 记载配置文件
    logManager.readConfiguration(resourceAsStream);

    // 获取日志记录器对象
    Logger logger = Logger.getLogger("com.chenpi.ChenPiJULMain");

    // 日志记录输出
    logger.severe(">>>>> Hello ChenPi!!");
    logger.warning(">>>>> Hello ChenPi!!");
    logger.info(">>>>> Hello ChenPi!!");
    logger.config(">>>>> Hello ChenPi!!");
    logger.fine(">>>>> Hello ChenPi!!");
    logger.finer(">>>>> Hello ChenPi!!");
    logger.finest(">>>>> Hello ChenPi!!");

    // 获取日志记录器对象
    Logger personLogger = Logger.getLogger("com.chenpi.person");

    // 日志记录输出
    personLogger.severe(">>>>> Hello Person!!");
    personLogger.warning(">>>>> Hello Person!!");
    personLogger.info(">>>>> Hello Person!!");
    personLogger.config(">>>>> Hello Person!!");
    personLogger.fine(">>>>> Hello Person!!");
    personLogger.finer(">>>>> Hello Person!!");
    personLogger.finest(">>>>> Hello Person!!");
  }
}

The output in the console and file is as follows:

Custom Logger

We can configure a Logger individually, such as log level, associated Handler, etc., instead of inheriting from the parent by default.

Of course, we can also configure the Logger with the package name, so that all child Loggers under this package name can inherit this configuration.

############################################################
# 全局属性
############################################################

# 顶级RootLogger关联的Handler,多个Handler使用逗号隔开
# 对于其他Logger,如果没有指定自己的Handler,则默认继承此
handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler

# 默认全局日志级别,Logger和Handler都可以设置自己的日志级别来覆盖此级别
.level= ALL
############################################################
# Handler 配置
############################################################

# FileHandler定义
# 日志文件存储位置
java.util.logging.FileHandler.pattern = ./jul%u.log
# 单个文件的最大字节数,0代表不限制
java.util.logging.FileHandler.limit = 50000
# 文件数量上限
java.util.logging.FileHandler.count = 5
# 日志级别
java.util.logging.FileHandler.level = SEVERE
# 日志追加方式
java.util.logging.FileHandler.append = true
# Handler对象采用的字符集
java.util.logging.FileHandler.encoding = UTF-8
# 日志格式,使用系统默认的简单格式
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter

# ConsoleHandler定义
# 日志级别
java.util.logging.ConsoleHandler.level = INFO
# Handler对象采用的字符集
java.util.logging.ConsoleHandler.encoding = UTF-8
# 日志格式,使用系统默认的简单格式
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
############################################################
# Logger 配置
############################################################

# 设置名称为com.chenpi.person的Logger对象的日志级别为WARNING
com.chenpi.person.level = WARNING
# 只关联FileHandler
com.chenpi.person.handlers = java.util.logging.FileHandler
# 关闭默认配置
com.chenpi.person.useParentHandlers = false
package com.chenpi;

import java.io.IOException;
import java.io.InputStream;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import org.junit.Test;

/**
* @author 陈皮
* @version 1.0
* @description
* @date 2022/3/2
*/
public class ChenPiJULMain {
 
    // JUL 日志框架演示
    @Test
    public void testLog() throws IOException {
 
        // 读取配置文件
        InputStream resourceAsStream = ChenPiJULMain.class.getClassLoader()
            .getResourceAsStream("logging.properties");
        // 获取LogManager
        LogManager logManager = LogManager.getLogManager();
        // 记载配置文件
        logManager.readConfiguration(resourceAsStream);
 
        // 获取日志记录器对象
        Logger logger = Logger.getLogger("com.chenpi.ChenPiJULMain");
 
        // 日志记录输出
        logger.severe(">>>>> Hello ChenPi!!");
        logger.warning(">>>>> Hello ChenPi!!");
        logger.info(">>>>> Hello ChenPi!!");
        logger.config(">>>>> Hello ChenPi!!");
        logger.fine(">>>>> Hello ChenPi!!");
        logger.finer(">>>>> Hello ChenPi!!");
        logger.finest(">>>>> Hello ChenPi!!");
 
        // 获取日志记录器对象
        Logger personLogger = Logger.getLogger("com.chenpi.person");
 
        // 日志记录输出
        personLogger.severe(">>>>> Hello Person!!");
        personLogger.warning(">>>>> Hello Person!!");
        personLogger.info(">>>>> Hello Person!!");
        personLogger.config(">>>>> Hello Person!!");
        personLogger.fine(">>>>> Hello Person!!");
        personLogger.finer(">>>>> Hello Person!!");
        personLogger.finest(">>>>> Hello Person!!");
    }
}

In the above example, for the Logger named com.chenpi.person, only the log is output to the file, and other Loggers are output to the console and the file at the same time.

Custom log format

Taking the SimpleFormatter class source code as an example, and then going to the LoggingSupport class source code, we find that we first determine whether we have configured the format through the java.util.logging.SimpleFormatter.format property, and if not, use the default log format.

So we can customize the format of logging in the configuration file.

############################################################
# 全局属性
############################################################

# 顶级RootLogger关联的Handler,多个Handler使用逗号隔开
# 对于其他Logger,如果没有指定自己的Handler,则默认继承此
handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler

# 默认全局日志级别,Logger和Handler都可以设置自己的日志级别来覆盖此级别
.level= ALL
############################################################
# Handler 配置
############################################################

# FileHandler定义
# 日志文件存储位置
java.util.logging.FileHandler.pattern = ./jul%u.log
# 单个文件的最大字节数,0代表不限制
java.util.logging.FileHandler.limit = 1024
# 文件数量上限
java.util.logging.FileHandler.count = 5
# 日志级别
java.util.logging.FileHandler.level = FINE
# 日志追加方式
java.util.logging.FileHandler.append = true
# Handler对象采用的字符集
java.util.logging.FileHandler.encoding = UTF-8
# 日志格式,使用系统默认的简单格式
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
# 自定义SimpleFormatter的日志格式
java.util.logging.SimpleFormatter.format = %4$s: %5$s [%1$tc]%n

# ConsoleHandler定义
# 日志级别
java.util.logging.ConsoleHandler.level = INFO
# Handler对象采用的字符集
java.util.logging.ConsoleHandler.encoding = UTF-8
# 日志格式,使用系统默认的简单格式
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
############################################################
# Logger 配置
############################################################

# 设置名称为com.chenpi.person的Logger对象的日志级别为WARNING
com.chenpi.person.level = WARNING

In this way, all log records of all SimpleFormatter-bound Handlers use the custom format.

Of course, through the source code, there are several other implementations of the log format class, as follows:

log filter

Filter, log filter, used to filter the output log records. We can filter according to multiple dimensions, such as outputting only the log whose message contains a certain piece of text information, only outputting the log recorded in a certain method, the log of a certain level, and so on.

The Logger object wraps the log information into a LogRecord object, and then passes the object to the Handler for processing. Each Logger and Handler can be associated with a Filter. LogRecord contains the text information of the log, the timestamp of the log generation, which class the log comes from, which method the log comes from, which thread the log comes from, and so on.

The source code of Filter is shown below. We only need to create an implementation class of Filter and rewrite the method.

package java.util.logging;

/**
* A Filter can be used to provide fine grain control over
* what is logged, beyond the control provided by log levels.
* <p>
* Each Logger and each Handler can have a filter associated with it.
* The Logger or Handler will call the isLoggable method to check
* if a given LogRecord should be published.  If isLoggable returns
* false, the LogRecord will be discarded.
*
* @since 1.4
*/
@FunctionalInterface
public 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);
}

We implement a Filter, if the log message contains the word violence, it will not be released, that is, this log will not be recorded.

package com.chenpi;

import java.util.logging.Filter;
import java.util.logging.LogRecord;

/**
 * @author 陈皮
 * @version 1.0
 * @description
 * @date 2022/3/2
 */
public class MyLoggerFilter implements Filter {

  private static final String SENSITIVE_MESSAGE = "暴力";

  @Override
  public boolean isLoggable(LogRecord record) {
    String message = record.getMessage();
    return null == message || !message.contains(SENSITIVE_MESSAGE);
  }
}

Then we can bind this filter object setting to the Logger.

package com.chenpi;

import java.io.IOException;
import java.io.InputStream;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import org.junit.Test;

/**
 * @author 陈皮
 * @version 1.0
 * @description
 * @date 2022/3/2
 */
public class ChenPiJULMain {

  // JUL 日志框架演示
  @Test
  public void testLog() throws IOException {

    // 读取配置文件
    InputStream resourceAsStream = ChenPiJULMain.class.getClassLoader()
        .getResourceAsStream("logging.properties");
    // 获取LogManager
    LogManager logManager = LogManager.getLogManager();
    // 记载配置文件
    logManager.readConfiguration(resourceAsStream);

    // 获取日志记录器对象
    Logger logger = Logger.getLogger("com.chenpi.ChenPiJULMain");
    // Logger关联过滤器
    logger.setFilter(new MyLoggerFilter());

    // 日志记录输出
    logger.info(">>>>> Hello ChenPi!!");
    logger.info(">>>>> 暴力小孩!!");
  }
}

// 输出结果如下
信息: >>>>> Hello ChenPi!! [星期三 三月 02 15:31:06 CST 2022]

 

Click Follow to learn about HUAWEI CLOUD's new technologies for the first time~

{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/4526289/blog/5519619