Explicación detallada del marco de registro de Java JUL

¡Acostúmbrate a escribir juntos! Este es el cuarto día de mi participación en el "Nuevo plan diario de los Nuggets·Desafío de actualización de abril", haz clic para ver los detalles del evento .

Introducción a julio

JUL (Java util logging), un marco de registro nativo de Java, no requiere la introducción de dependencias de terceros y es simple y conveniente de usar. Generalmente se usa en aplicaciones pequeñas y rara vez se usa en proyectos principales.

JUL Arquitectura

inserte la descripción de la imagen aquí

  • Aplicación: aplicación Java.
  • Registrador: un registrador, una aplicación Java publica registros llamando a los métodos del registrador.
  • Controlador: controlador, cada registrador se puede asociar con uno o más controladores, el controlador obtiene registros del registrador y envía los registros a un destino determinado, el destino puede ser la consola, el archivo local o el servicio de registro de red, o enviarlos a los registros del sistema operativo y así. Un controlador se puede deshabilitar a través Handler.setLevel(level.off)de métodos, o se pueden establecer otros niveles para habilitar este controlador.
  • Filtro: filtro, qué registros de registro filtrar en función de las condiciones. Cada registrador y controlador se puede asociar con un filtro.
  • Formateador: un formateador responsable de transformar y formatear registros de registro en eventos de registro.
  • Nivel: cada registro de registro tiene un nivel de registro asociado, que indica la importancia y la urgencia del registro. También es posible establecer el nivel de registro asociado para Logger y Handler.

Ejemplo de introducción

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 envía información de registro a la consola de forma predeterminada y el nivel de registro predeterminado es info. La salida de la consola es la siguiente:

inserte la descripción de la imagen aquí

Registrador relación de herencia padre-hijo

En JUL, Logger tiene el concepto de relación de herencia padre-hijo, y la relación de herencia padre-hijo se divide según la relación de inclusión del nombre del objeto Logger. Para un objeto Logger, si no se puede encontrar el objeto Logger principal creado explícitamente, entonces su Logger principal es el Logger raíz, es decir, RootLogger.

子代 Logger 对象会继承父 Logger 的配置,例如日志级别,关联的 Handler,日志格式等等。对于任何 Logger 对象,如果没对其做特殊配置,那么它最终都会继承 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
复制代码

日志配置

我们可以通过2种方式调整 JUL 默认的日志行为(设置日志级别,日志输出目的地,日志格式等等),一种是通过在程序中硬编码形式(不推荐),另一种是通过单独的配置文件形式。

硬编码日志配置

在 JUL 中,Logger 是有父子继承关系的,所以当我们需要对某一个 Logger 对象进行单独的配置时,需要将它设置为不继承使用父 Logger 的配置。

以下演示名称为com.chenpi.ChenPiJULMain的 Logger 对象单独进行配置,并且关联两个 Handler。

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!!");
  }
}
复制代码

因为 Logger 设置的日志级别是 ALL,即所有级别的日志记录都可以通过。但是 ConsoleHandler 设置的日志级别是 INFO,所以控制台只输出 INFO 级别以上的日志记录。而 FileHandler 设置的日志级别是 FINE,所以日志文件中输出的是 FINE 级别以上的日志记录。

以下是控制台输出的日志结果:

inserte la descripción de la imagen aquí

以下是日志文件jul.log中输出的日志结果:

inserte la descripción de la imagen aquí

日志配置文件

通过 debug 调试,按以下方法顺序,可以发现,如果我们没有配置 JUL 的配置文件,系统默认从 JDK 的安装目录下的 lib 目录下读取默认的配置文件logging.properties

getLogger()  -> demandLogger() -> LogManager.getLogManager() -> ensureLogManagerInitialized() -> owner.readPrimordialConfiguration() -> readConfiguration()
复制代码

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

以下是 JDK 自带的 JUL 默认日志配置文件内容:

inserte la descripción de la imagen aquí

对于配置选项有哪些,其实可以通过相应类的源码找出,例如 Logger 类的源码如下:

inserte la descripción de la imagen aquí

FileHandler 类的源码如下:

inserte la descripción de la imagen aquí

所以我们可以拷贝默认的配置文件到我们工程的 resources 目录下,自定义修改配置信息。

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

# 顶级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
复制代码

然后我们应用中加载我们类路径中自定义的配置文件。

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!!");
  }
}
复制代码

控制台和文件中输出内容如下:

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

自定义 Logger

我们可以针对某一个 Logger 进行单独的配置,例如日志级别,关联的 Handler 等,而不默认继承父级的。

当然我们也可以为以包名为名称的 Logger 进行配置,这样这个包名下的所有子代 Logger 都能继承此配置。

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

# 顶级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!!");
    }
}
复制代码

上述例子中,对于名称为com.chenpi.person的 Logger,只将日志输出到文件中,其他 Logger 则会同时输出到控制台和文件中。

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

自定义日志格式

SimpleFormatter类源码为例,再到LoggingSupport类源码,发现首先判断我们是否通过java.util.logging.SimpleFormatter.format属性配置了格式,如果没有则使用默认的日志格式。

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

所以我们可以在配置文件中自定义日志记录的格式。

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

# 顶级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
复制代码

这样,所有绑定了 SimpleFormatter 的 Handler 的所有日志记录就使用了自定义的格式。

inserte la descripción de la imagen aquí

当然通过源码发现,日志格式类还有其他几个实现,如下所示:

inserte la descripción de la imagen aquí

日志过滤器

Filter,日志过滤器,用来对输出的日志记录进行过滤。我们可以根据多个维度进行过滤,例如只输出 message 包含某段文本信息的日志,只输出某个方法中记录的日志,某个级别的日志等等。

Logger 对象将日志信息包装成一个 LogRecord 对象,然后将该对象传给 Handler 进行处理。每一个 Logger 和 Handler 都可以关联一个 Filter。LogRecord 中包含了日志的文本信息、日志生成的时间戳、日志来自于哪个类、日志来自于哪个方法、日志来自于哪个线程等等信息。

Filter 源码如下所示,我们只需要创建一个 Filter 的实现类,重写方法即可。

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);
}
复制代码

我们实现一个 Filter ,如果日志消息包含暴力两个字,则不予放行,即不记录此条日志。

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);
  }
}
复制代码

然后我们将此过滤器对象设置绑定到 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]
复制代码

本次分享到此结束啦~~

Soy Chen Pi , un ITer en codificación de Internet . Si cree que el artículo es útil para usted, como, favorito, seguir, comentar, ¡su apoyo es la mayor motivación para mi creación!

Supongo que te gusta

Origin juejin.im/post/7083875248344399903
Recomendado
Clasificación