java日志框架系列-02_jul

/ 默认分类 / 没有评论 / 979浏览

alt

概述

在前一篇java日志框架系列-01_基本概念中介绍了一个日志系统的核心概念和设计时需要考虑的问题,这个都属于设计或者说接口层面的定义,接下来会逐个分析基于这个设计的具体实现,距离来说目前主要的实现包括jul,log4j,jcl,log4j2,logback,slf4j,后续系列文章会从基础使用,核心配置,核心特征角度分析这些不同实现的特征,以及它们之间的关系。

首先本篇会介绍jul,即java.util.logging的简称,这个是java1.4开始自带的日志实现,使用时不需要依赖其他jar包

官方文档

Logger文档,主要介绍了logger基于命名的继承层级组织方式,日志级别控制,日志输出类型(普通log,附带精确信息的logp方法<触发类,调用方法名等信息>,logrb, 不那么精确的信息比如行号) https://docs.oracle.com/javase/8/docs/api/java/util/logging/Logger.html

LogManager的文档,主要包含配置文件查找逻辑,配置文件核心配置规则 https://docs.oracle.com/javase/8/docs/api/java/util/logging/LogManager.html

概念关系

标准概念jul实现
Loggerjava.util.logging.Logger log =Logger.getLogger(JULTest.class.getName());
LoggerEventjava.util.logging.LogRecord,核心属性比如线程Id,loggerName,message,时间戳,异常栈等都有
Priorityjava.util.logging.Level 枚举包括 从高到低为OFF(2^31-1)—>SEVERE(1000)—>WARNING(900)—>INFO(800)—>CONFIG(700)—>FINE(500)—>FINER(400)—>FINEST(300)—>ALL(-2^31)
Appenderjava.util.logging.SocketHandler, java.util.logging.FileHandler, java.util.logging.ConsoleHandler
Fomatterjava.util.logging.XMLFormatter,java.util.logging.SimpleFormatter

其中LoggerEvent,核心属性比如线程Id,loggerName,message,时间戳,异常栈等都有,具体参考定义

/**
 * @serial Logging message level
 */
private Level level;

/**
 * @serial Sequence number
 */
private long sequenceNumber;

/**
 * @serial Class that issued logging call
 */
private String sourceClassName;

/**
 * @serial Method that issued logging call
 */
private String sourceMethodName;

/**
 * @serial Non-localized raw message text
 */
private String message;

/**
 * @serial Thread ID for thread that issued logging call.
 */
private int threadID;

/**
 * @serial Event time in milliseconds since 1970
 */
private long millis;

/**
 * @serial The Throwable (if any) associated with log message
 */
private Throwable thrown;

/**
 * @serial Name of the source Logger.
 */
private String loggerName;

/**
 * @serial Resource bundle name to localized log message.
 */
private String resourceBundleName;

配置文件

默认配置文件路径为 $JAVA_HOME/jre/lib/logging.properties 文件解析位置java.util.logging.LogManager#readConfiguration() 这个文件可以在代码中动态加载,也可以通过运行时参数指定文件路径

示例配置

jul.properties内容:

handlers= java.util.logging.ConsoleHandler
.level= INFO
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format= [%1$tF %1$tT] [%4$-7s] %3$s %5$s %n

其中格式定义可以参考java.util.Formatter的文档https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html

比如上面的配置中指定了logger的输出由ConsoleHandler负责,全局日志级别为INFO,

ConsoleHandler的格式化方式为SimpleFormatter,

这个SimpleFormatter的格式为[%1$tF %1$tT] [%4$-7s] %3$s %5$s %n

通过代码读取并配置日志

// 读取配置文件,通过类加载器
InputStream ins = JULTest.class.getClassLoader().getResourceAsStream("jul.properties");
// 创建LogManager
LogManager logManager = LogManager.getLogManager();
// 通过LogManager加载配置文件
try {
    logManager.readConfiguration(ins);
} catch (IOException e) {
    e.printStackTrace();
}

通过运行时参数指定配置文件

可以通过jvm参数指定配置文件。比如自定义的配置文件为jul.properties,应用启动通过

-Djava.util.logging.config.file=/xx/xxx/jul.properties

这种方式指定,注意需要是绝对路径

示例测试

package top.easydo.log.jul;
 
import java.util.logging.Logger;
 
/**
 * @author think
 * @date 2020/12/01
 */
public class JULTest {
 
    public static void main(String[] args) {
        Logger log = Logger.getLogger(JULTest.class.getName());
        log.info("jul测试,info");
        log.warning("jul测试,warning");
        log.config("jul测试,config");
        log.fine("jul测试,fine");
        log.finer("jul测试,finer");
        log.finest("jul测试,finest");
    }
}

上面的代码运行后输出如下 alt

代码中输出了各个级别的日志,但是实际只输出了2行日志,这个因为配置文件中全局级别为.level = INFO, ConsoleHandler.level=ALL,这样ConsoleHandler可以输出所有级别日志,

但是实际具体日志级别还得看logger的级别,因为JULTest这个logger的级别没有指定,它就会继承root的级别,这里就是INFO,所以只会展示INFO和INFO之上的日志,这里就是WARNING

如果修改.level=ALL就能看到各个级别的输出日志

配置文件中格式指定的是 [%1$tF %1$tT] [%4$-7s] %3$s %5$s %n

这个格式中通过 index$ 这种方式确定第index个参数的格式,比如 %1$tF %1$tT 表示的就是第1个参数的格式,%5$s表示的就是第5个参数的格式

跟踪log.info方法,最后格式化消息的位置如下:

java.util.logging.SimpleFormatter#format
 
return String.format(format,
                     dat,
                     source,
                     record.getLoggerName(),
                     record.getLevel().getLocalizedLevelName(),
                     message,
                     throwable);

可以看到SimpleFormatter只会输出从dat开始(index=1)的6个日志参数信息