jdk的日志框架

Nov 17, 2017 • kael


前言

目前java世界里的日志框架很多,占主流的是下面四个:

  • Log4j/Log4j2: Apache Log4j是一个基于Java的日志记录工具。它是由Ceki Gülcü首创的,现在则是Apache软件基金会的一个项目。 Log4j是几种Java日志框架之一。
  • Commons Logging: Apache基金会所属的项目,是一套Java日志接口,之前叫Jakarta Commons Logging,后更名为Commons Logging。
  • Slf4j:类似于Commons Logging,是一套简易Java日志门面,本身并无日志的实现。(Simple Logging Facade for Java,缩写Slf4j)。
  • Jul:(Java Util Logging),自Java1.4以来的官方日志实现。

事实上这造成了一种java日志系统混乱,没有标准的现状,不过从目前的市场情况上看,slf4j应用更加广泛,因此更加建议在新项目中使用slf4j作为统一日志标准。

在实际选择日志框架的时候,我比较建议按照如下两种场景选择:

  • 开发公共模块包:使用jul,由于是jdk自带的日志框架,使用这个日志框架的最大好处在于不用引入新的依赖,不会对依赖这个模块包的程序使用的日志框架有限制,另外,外部如果使用slf4j日志框架的话,还能在外边做桥接,在不改代码的情况下让日志自动输出到slf4j上。
  • 开发项目:开发项目的时候,建议使用slf4j作为日志标准,使用logback作为实现。

本文主要介绍如何使用jul,并附上如何在外部将jul桥接到slf4j上。

Java util logging

jul是jdk1.4版本以后的日志框架,放在java.util.logging包中。

创建日志对象

获取一个日志对象:

import java.util.logging.Logger;
// 假设我们定义了一个类MyLogger.java
private final static Logger LOGGER = Logger.getLogger(MyLogger.class.getName());

这里创建的日志对象实际上是一个有层级的日志对象,通过.划分层级。

假设创建了一个namecom.example的logger,那么这个logger是com这个logger的子,而com是空字符串""的子。

我们可以通过设置父logger的属性来让子logger自动继承。

如果想要设置全局的属性,一般设置名为空字符串的logger即可。

日志等级

jul中定义了如下几个等级的日志(从上到下等级递减):

  • SEVERE (最高级)
  • WARNING
  • INFO
  • CONFIG
  • FINE
  • FINER
  • FINEST

另外,jul还定义了OFFALL两个等级,分别表示全部关闭和全部打开。

在slf4j中,只有五个等级:TRACE, DEBUG, INFO, WARN, ERROR

slf4j等级 jul等级
TRACE FINER,FINEST
DEBUG FINE,CONFIG
INFO INFO
WARN WARNING
ERROR SEVERE

设置日志等级方式如下:

LOGGER.setLevel(Level.INFO);

这里设置的等级会被子日志对象继承。

Handler

每一个日志对象都可以设置多个处理器java.util.logging.Handler

每一个handler都会收到日志消息,并将日志写到对应的目标,比如控制台或者文件,当然也能定制写到网络或者其他地方。

每个handler可以通过getLevel()setLevel(Level)来设置自己处理的日志级别。

在jdk中已经默认实现了两个handler:

  • ConsoleHandler: 写到控制台
  • FileHandler: 写到文件

日志的jdk默认配置会将INFO级别的日志交给ConsoleHandler处理。

Formatter

每一个handler可以设置一个格式化器,将日志内容格式化输出(如转换成html格式,或者增加时间信息等)。

jdk默认提供了两个格式化器:

  • SimpleFormatter: 普通文本输出
  • XMLFormatter: 格式化成xml输出

当然我们可以实现自己的格式化器,只要继承抽象类java.util.logging.Formatter并实现抽象接口String format(LogRecord record),然后设置为handler的格式化器即可,如:

ConsoleHandler h = new ConsoleHandler();
Formatter f = new Formatter(){
    @Override
    public String format(LogRecord record){
        return record.getMessage();
    }
};
handler.setFormatter(f);
LOGGER.addHandler(h);

java.util.logging.LogRecord是日志信息的封装对象。

Log Manager

jul中,有一个重要的类java.util.logging.LogManager,它主管logger对象的创建和配置管理。

我们可以通过这个类来设置所有logger的配置,如:

// 设置`com.example`的logger的等级为FINE,则所有对应的子logger的等级都会变成FINE
LogManager.getLogManager().getLogger("").setLevel(Level.FINE);

LogManager对象还能读取配置文件的信息(一般在程序启动时执行)完成对生成的日志对象的配置,使用如下两个接口:

LogManager.getLogManager().readConfiguration(); (1)
LogManager.getLogManager().readConfiguration(InputStream is); (2)
  • (1)

默认情况下,先检查系统参数java.util.logging.config.file指定的文件作为配置文件,如果没有知道,则读取{JAVA_HOME}/lib/logging.properties作为配置文件。

  • (2)

使用自己指定的输入流作为配置文件。

Configure

jul的配置文件是一个properties文件,这里简单介绍几个常用的配置项:

# 默认的handler,多个的话用逗号分隔
handlers= java.util.logging.ConsoleHandler
# 全局默认的日志等级
.level= INFO
# 前面指定的handler的处理日志等级和格式化器
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

外部使用slf4j重定向jul的日志输出到slf4j

当我们使用的依赖包使用jul来输入日志时,我们可以通过slf4j的外部配置,来将日志全部重定向到slf4j的接口,以便统一日志。

增加maven依赖:

<!-- slf4j的接口 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>
<!-- jul桥接包 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jul-to-slf4j</artifactId>
    <version>1.7.25</version>
</dependency>

启用jul重定向到slf4j

需要使用如下代码:

// 重置所有配置,使得依赖包内部代码对日志的配置失效。
LogManager.getLogManager().reset();
// 清理所有jul的handler
SLF4JBridgeHandler.removeHandlersForRootLogger();
// 安装slf4j的handler
SLF4JBridgeHandler.install();
// 非必需:设置jul的日志级别,如果不设置的话,内部代码日志级别低于INFO的都默认不会输出
LogManager.getLogManager().getLogger("").setLevel(Level.FINEST);