JUL日志框架

JUL日志框架

1、JUL 简单介绍

JUL全称Java util logging是Java原生的日志框架,使用时不需要另外引用第三方类库,相对其他日志框 架使用方便,学习简单,能够在小型应用中灵活使用。

在JUL中有以下组件:

  1. Loggers:被称为记录器,应用程序通过获取Logger对象,调用其API来来发布日志信息。Logger通常时应用程序访问日志系统的入口程序
  2. Handlers:也被称为Appenders,每个Logger都会关联一组Handlers,Logger会将日志交给关联的Handler处理,由Handler负责将日志做记录。Handlers在此是一个抽象,其具体的实现决定了日志记录的位置可以是控制台、文件、网络上的其他日志服务或操作系统日志等
  3. Formatters:也被称为Layouts,它负责对日志事件中的数据进行转换和格式化。它决定了数据在一条日志记录中的最终形式
  4. Level:日志的输出级别,每条日志消息都有一个关联的级别。我们根据输出级别的设置,来展现最终所呈现的日志信息,根据不同的需求设置不同级别
  5. Filters:过滤器,根据需要定制哪些信息会被记录,哪些信息会被放过

总结一下就是:

用户使用Logger来进行日志记录,Logger持有若干个Handler,日志的输出操作是由Handler完成的。 在Handler在输出日志前,会经过Filter的过滤,判断哪些日志级别过滤放行哪些拦截,Handler会将日志内容输出到指定位置(日志文件、控制台等)。Handler在输出日志时会使用Layout,将输出内容进行排版。

JUC 流程示意图:

┌──────────────────────────────────────────────────────────────────────────────────────┐
│             LogManager─┐     Level            Filter         Formatter               │
│                        ⬇       ⬇                 ⬇               ⬇                   │
│  Application-------->Logger-------->LogRecord-------->Handler-------->Outside World  │
└──────────────────────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────────────────────┐
│    应用程序    运行  日志记录器    记录日志   日志处理器     处理日志                      │
│  Application-------->Logger---------------->Handler------------------>Outside World  │
│                         ⬇                     ⬇                                      │
│   ┌──────────────────────────────────────────────────────────────┐                   │
│   │Filters(日志过滤器), Formatters(日志格式化组件), Level日志输出级别│                   │
│   └──────────────────────────────────────────────────────────────┘                   │
└──────────────────────────────────────────────────────────────────────────────────────┘

JUC 总体流程:

  1. 初始化LogManager,LogManager加载logging.properties配置,添加Logger到LogManager中
  2. 从单例LogManager获取Logger
  3. 设置级别Level,并指定日志记录LogRecord
  4. Filter提供了日志级别之外更细粒度的控制
  5. Handler是用来处理日志输出位置
  6. Formatter是用来格式化LogRecord的

JUL 日志原理解析:

  1. Logger:是用来记录日志的,负责调用其有的Handler进行日志输出
  2. LogRecord:是用来传递日志的,一条具体的日志对象,比如这条日志所在线程、日志级别,日志名称,日志内容,时间等信息
  3. Handler:是用来导出(处理)日志的,负责具体的日志输出,有不同的实现类,比如(ConsoleHandler 控制台输出,FileHandler 文件输出)
  4. Level:是用来定义日志级别的(控制日志输出)
  5. Filter:提供了日志级别之外更细粒度的控制
  6. Formatter:是用来格式化LogRecord的,Handler在输出前会调用Formatter 对LogRecord 进行格式化,如简单字符串格式化,Html格式化,XML格式化等
  7. 此外还有一个单一的全局的LogManager维护Logger和管理日志(LogManager 负责创建日志对象,并将创建的日志对象继承RootLogger的默认配置)。JUL主要由这七个核心类或接口组成,再有就是一些子类或者实现类。API中关于这几个类或接口的类和方法的详细介绍这里不再复述。现在我们已经对JUL有了一个整体的了解,并且对核心类或接口的功能职责也有了初步了解,接下就该结合代码看看JUL的真面目。

在任何日志框架中都会有继承性的概念,在获取Logger对象时,默认会继承RootLogger的配置,但自己的Logger也可以单独设置级别,Handler等。但是如果日志名称是包名进行命名的话,子包默认会继承父包的配置,例如 指定了com.a.b这个包的Handler是ConsoleHandler,Level是info,则com.a.b.c或者 com.a.b.c.d等属于这个包的Logger都会默认继承这个包的配置

2、JUL 入门案例

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

public class Test {
    //1、获取日志记录器对象
    public static Logger logger = Logger.getLogger(Test.class.getName());
    public static void main(String[] args) {
        /**
         * 对于日志输出有两种方式
         *   1.一种是指定日志级别方法输出(支持占位符输出)
         *   2.另一种是通用方法输出,需要参数指定日志级别(不支持占位符输出)
         */
        // 1.通用方法输出,需要参数指定日志级别
        logger.log(Level.SEVERE, "this is a severe");
        logger.log(Level.WARNING, "this is a warn");
        logger.log(Level.INFO, "this is a info"); // JUL默认日志级别,比info高的输出,比info低的过滤掉
        logger.log(Level.CONFIG, "this is a config");
        logger.log(Level.FINE, "this is a fine");
        logger.log(Level.FINER, "this is a finer");
        logger.log(Level.FINEST, "this is a finest");
        logger.log(Level.INFO, "this is a info", new Exception("My Exception")); // 带异常输出

        // 2.指定日志级别方法输出,有不同的级别,可带参数,其它类似
        logger.severe("severe log");
        logger.warning("warning log");
        logger.info("info log"); // JUL默认日志级别,比info高的输出,比info低的过滤掉
        logger.config("config log");
        logger.fine("fine log");
        logger.finer("finer log");
        logger.finest("finest log");

        // 3.占位符输出, 只有.log()方法是支持占位符输出的
        logger.log(Level.WARNING, "this is a warn, {0} ", "p1");
        logger.log(Level.INFO, "this is a info, {0} {1}", new Object[]{"p1","p2"});
    }
}

// 输出内容
326, 2022 11:21:58 上午 Test main
严重: this is a severe
326, 2022 11:21:58 上午 Test main
警告: this is a warn
326, 2022 11:21:58 上午 Test main
信息: this is a info
326, 2022 11:21:58 上午 Test main
信息: this is a info
java.lang.Exception: My Exception
	at Test.main(Test.java:21)

326, 2022 11:21:58 上午 Test main
严重: severe log
326, 2022 11:21:58 上午 Test main
警告: warning log
326, 2022 11:21:58 上午 Test main
信息: info log
326, 2022 11:21:58 上午 Test main
警告: this is a warn, p1 
326, 2022 11:21:58 上午 Test main
信息: this is a info, p1 p2

因为默认的日志级别是INFO,所以比INFO级别低的 CONFIG、FINE、FINER、FINEST的日志记录消息没有记录。

3、JUL 组件介绍

JUL常用组件如下:

  • Level:日志级别
  • Handler:日志输出地
  • Formatter:日志格式化

1、Level(日志级别)

日志级别由高到低:OFF/SEVERE/WARNIN/INFO/CONFIG/FINE/FINERG/FINEST/ALL,每个级别有自己的数值,在java.util.logging.Level类中源码如下:

public static final Level OFF = new Level("OFF",Integer.MAX_VALUE, defaultBundle);
public static final Level SEVERE = new Level("SEVERE",1000, defaultBundle);
public static final Level WARNING = new Level("WARNING", 900, defaultBundle);
public static final Level INFO = new Level("INFO", 800, defaultBundle);
public static final Level CONFIG = new Level("CONFIG", 700, defaultBundle);
public static final Level FINE = new Level("FINE", 500, defaultBundle);
public static final Level FINER = new Level("FINER", 400, defaultBundle);
public static final Level FINEST = new Level("FINEST", 300, defaultBundle);
public static final Level ALL = new Level("ALL", Integer.MIN_VALUE, defaultBundle);
  • Level.SEVERE:最高级别的日志,主要记录错误信息
  • Level.WARNING:级别排行第二,记录警告信息
  • Level.INFO:级别排行第三,最常用的日志级别,记录普通消息,比如请求信息、连接信息、参数信息等等
  • Level.CONFIG:级别排行第四,记录配置信息,加载配置文件、读取配置参数都可以使用CONFIG记录
  • Level.FINE :debug 时记录的 日志消息,记录运行时的状态、传递的参数等等
  • Level.FINER:debug 时记录的 日志消息,记录运行时的状态、传递的参数等等
  • Level.FINEST:debug 时记录的 日志消息,记录运行时的状态、传递的参数等等
  • Level.OFF: 关闭日志记录
  • Level.ALL:所有记录都开启

可自定义日志级别:继承java.util.logging.Level类即可

2、Handler(日志输出)

日志输出的目的,java.util.logging包中有如下四种(ConsoleHandler、FileHandler、SocketHandler、MemoryHandler):

  1. StreamHandler(父类):
    1. ConsoleHandler:输出到控制台,默认Level为INFO,流为System.err,Formatter为SimpleFormatter
    2. FileHandler:输出到文件,默认Level为INFO,Formatter为XMLFormatter
    3. SocketHandler:输出到socket
  2. MemoryHandler:输出到内存

可自定义日志输出:继承java.util.logging.Handler类即可

3、Formatter(日志格式化)

定义日志输出的格式,目前有SimpleFormatter和XMLFormatter两种格式,分别对应简单格式和xml格式。

注意:默认情况下:ConsoleHandler对应SimpleFormatter,FileHandler对应XMLFormatter。

可自定输出格式:继承抽象类java.util.logging.Formatter即可

4、自定义日志输出

1、自定义输出日志级别(控制台)

Java默认的只能输出3个级别,并且默认的还是输出到控制台。我们现在想要输出其他多个级别,只能自定义输出级别了。自定义的时候,先要关闭默认的。

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

public class Test {
    public static void main(String[] args) {
        // 1.获取日志记录器对象
        Logger logger = Logger.getLogger(Test.class.getName());
        // 2.关闭系统默认配置(需要自定义日志配置必须关闭)
        logger.setUseParentHandlers(false);

        // 3.自定义配置日志级别
        // 创建ConsoleHandler控制台输出
        ConsoleHandler consoleHandler = new ConsoleHandler();
        // 创建简单格式转换对象
        SimpleFormatter simpleFormatter = new SimpleFormatter();
        // 进行关联
        consoleHandler.setFormatter(simpleFormatter);
        logger.addHandler(consoleHandler);
        // 配置日志具体级别,注意:Logger与Handler都需要配置
        logger.setLevel(Level.ALL);
        consoleHandler.setLevel(Level.ALL);

        // 4.日志记录输出,可以发现设置级别为全部输出后,可以在控制台看到所有输出了
        logger.severe("severe");
        logger.warning("warning");
        logger.info("info");
        logger.config("config");
        logger.fine("fine");
        logger.finer("finer");
        logger.finest("finest");
    }
}

// 输出内容
326, 2022 11:56:15 上午 Test main
严重: severe
326, 2022 11:56:15 上午 Test main
警告: warning
326, 2022 11:56:15 上午 Test main
信息: info
326, 2022 11:56:15 上午 Test main
配置: config
326, 2022 11:56:15 上午 Test main
详细: fine
326, 2022 11:56:15 上午 Test main
较详细: finer
326, 2022 11:56:15 上午 Test main
非常详细: finest

2、自定义日志输出到文件(磁盘)

import java.io.IOException;
import java.util.logging.*;

public class Test {
    public static void main(String[] args) throws IOException {
        // 1.获取日志记录器对象
        Logger logger = Logger.getLogger(Test.class.getName());
        // 2.关闭系统默认配置(需要自定义日志配置必须关闭)
        logger.setUseParentHandlers(false);

        // 自定义配置日志Level、Handler、Formatter
        /**
         * 创建ConsoleHandler控制台输出
         * 并且设置为SimpleFormatter简单格式转换对象(默认就是SimpleFormatter)
         * 日志输出级别为ALL全部级别
         */
        ConsoleHandler consoleHandler = new ConsoleHandler();
        consoleHandler.setFormatter(new SimpleFormatter());
        logger.addHandler(consoleHandler);
        // 配置Handler日志具体级别
        consoleHandler.setLevel(Level.ALL);

        /**
         * 创建FileHandler文件输出
         * 并且设置为SimpleFormatter简单格式转换对象(默认是XMLFormatter)
         * 日志输出级别为ALL全部级别
         */
        // 第二个参数是日志输出内容追加,注意:目录必须提前存在,否则会抛异常
        FileHandler fileHandler = new FileHandler("./logs/jul.log", true);
        fileHandler.setFormatter(new SimpleFormatter());
        logger.addHandler(fileHandler);
        // 配置Handler日志具体级别
        fileHandler.setLevel(Level.ALL);

        // 配置Logger日志输出级别
        logger.setLevel(Level.ALL);

        // 3.日志记录输出,以下级别从大到小
        logger.severe("severe");
        logger.warning("warning");
        logger.info("info");
        logger.config("config");
        logger.fine("fine");
        logger.finer("finer");
        logger.finest("finest");
    }
}

// 输出内容(文件磁盘中也是同样的输出)
326, 2022 12:27:32 下午 Test main
严重: severe
326, 2022 12:27:32 下午 Test main
警告: warning
326, 2022 12:27:32 下午 Test main
信息: info
326, 2022 12:27:32 下午 Test main
配置: config
326, 2022 12:27:32 下午 Test main
详细: fine
326, 2022 12:27:33 下午 Test main
较详细: finer
326, 2022 12:27:33 下午 Test main
非常详细: finest

以上创建了两个Handler,一个是控制台的,一个是文件的,把这个Handler设置到日志记录器对象里面。以后的日志不仅可以输出控制台,还可以输出到文件

可以把文件输出Formatter改成默认的XMLFormatter,观察文件内容

FileHandler fileHandler = new FileHandler("./logs/jul.log", true);
fileHandler.setFormatter(new XMLFormatter()); // 这行代码可写可不写,因为FileHandler默认就是XMLFormatter

输出到文件的内容:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
  <date>2022-03-26T04:33:02.178157900Z</date>
  <millis>1648269182178</millis>
  <nanos>157900</nanos>
  <sequence>0</sequence>
  <logger>Test</logger>
  <level>SEVERE</level>
  <class>Test</class>
  <method>main</method>
  <thread>1</thread>
  <message>severe</message>
</record>
<record>
  <date>2022-03-26T04:33:02.228169900Z</date>
  <millis>1648269182228</millis>
  <nanos>169900</nanos>
  <sequence>1</sequence>
  <logger>Test</logger>
  <level>WARNING</level>
  <class>Test</class>
  <method>main</method>
  <thread>1</thread>
  <message>warning</message>
</record>
<record>
  <date>2022-03-26T04:33:02.229171100Z</date>
  <millis>1648269182229</millis>
  <nanos>171100</nanos>
  <sequence>2</sequence>
  <logger>Test</logger>
  <level>INFO</level>
  <class>Test</class>
  <method>main</method>
  <thread>1</thread>
  <message>info</message>
</record>
<record>
  <date>2022-03-26T04:33:02.232172300Z</date>
  <millis>1648269182232</millis>
  <nanos>172300</nanos>
  <sequence>3</sequence>
  <logger>Test</logger>
  <level>CONFIG</level>
  <class>Test</class>
  <method>main</method>
  <thread>1</thread>
  <message>config</message>
</record>
<record>
  <date>2022-03-26T04:33:02.233171500Z</date>
  <millis>1648269182233</millis>
  <nanos>171500</nanos>
  <sequence>4</sequence>
  <logger>Test</logger>
  <level>FINE</level>
  <class>Test</class>
  <method>main</method>
  <thread>1</thread>
  <message>fine</message>
</record>
<record>
  <date>2022-03-26T04:33:02.233171500Z</date>
  <millis>1648269182233</millis>
  <nanos>171500</nanos>
  <sequence>5</sequence>
  <logger>Test</logger>
  <level>FINER</level>
  <class>Test</class>
  <method>main</method>
  <thread>1</thread>
  <message>finer</message>
</record>
<record>
  <date>2022-03-26T04:33:02.234172500Z</date>
  <millis>1648269182234</millis>
  <nanos>172500</nanos>
  <sequence>6</sequence>
  <logger>Test</logger>
  <level>FINEST</level>
  <class>Test</class>
  <method>main</method>
  <thread>1</thread>
  <message>finest</message>
</record>
</log>

5、Logger之间的父子关系

JUL中Logger之间存在父子关系,这种父子关系通过树状结构存储,JUL在初始化时会创建一个顶层RootLogger作为所有Logger父Logger,存储上作为树状结构的根节点。并父子关系通过路径来关联。默认子Logger会继承父Logger的属性。

import java.io.IOException;
import java.util.logging.*;

public class Test {
    public static void main(String[] args) throws IOException {
        // 1.获取日志记录器对象, logger1 是 logger2 的父对象
        Logger logger1 = Logger.getLogger("com");
        Logger logger2 = Logger.getLogger("com.controller");

        // 2.测试
        System.out.println(logger1 == logger2.getParent()); // true
        // 所有日志记录器的顶级父元素 LogManager$RootLogger,name 为  ""
        System.out.println("logger1 Parent:"+logger1.getParent() + " ,name:" + logger1.getParent().getName());
        // 打印logger2的父类logger1的对象与名称
        System.out.println("logger2 Parent:"+logger2.getParent() + " ,name:" + logger2.getParent().getName());

        // 3.logger1父类:关闭默认配置, 并自定义配置日志级别和设置ConsoleHandler控制台输出
        logger1.setUseParentHandlers(false);
        ConsoleHandler consoleHandler = new ConsoleHandler();
        consoleHandler.setFormatter(new SimpleFormatter());
        consoleHandler.setEncoding("UTF-8");
        consoleHandler.setLevel(Level.ALL);
        logger1.addHandler(consoleHandler);
        logger1.setLevel(Level.ALL);

        // 4.使用logger2子类输出(查看是否继承父类的属性)
        logger2.severe("severe");
        logger2.warning("warning");
        logger2.info("info");
        logger2.config("config");
        logger2.fine("fine");
        logger2.finer("finer");
        logger2.finest("finest");
    }
}

// 输出内容
true
logger1 Parent:java.util.logging.LogManager$RootLogger@4b9e13df ,name:
logger2 Parent:java.util.logging.Logger@2b98378d ,name:com
326, 2022 12:57:31 下午 Test main
严重: severe
326, 2022 12:57:31 下午 Test main
警告: warning
326, 2022 12:57:31 下午 Test main
信息: info
326, 2022 12:57:31 下午 Test main
配置: config
326, 2022 12:57:31 下午 Test main
详细: fine
326, 2022 12:57:31 下午 Test main
较详细: finer
326, 2022 12:57:31 下午 Test main
非常详细: finest

从执行结果可以看出子类默认继承了父类Logger的配置。

注意:如果子类不想或者不需要继承父类Logger的配置,只需要关闭子类Logger默认配置,然后重新自定配置即可。

import java.io.IOException;
import java.util.logging.*;

public class Test {
    public static void main(String[] args) throws IOException {
        // 1.获取日志记录器对象, logger1 是 logger2 的父对象
        Logger logger1 = Logger.getLogger("com");
        Logger logger2 = Logger.getLogger("com.controller");

        // 2.logger1父类:关闭默认配置, 并自定义配置日志级别和设置ConsoleHandler控制台输出
        logger1.setUseParentHandlers(false);
        ConsoleHandler consoleHandler = new ConsoleHandler();
        consoleHandler.setFormatter(new SimpleFormatter());
        consoleHandler.setEncoding("UTF-8");
        consoleHandler.setLevel(Level.ALL);
        logger1.addHandler(consoleHandler);
        logger1.setLevel(Level.ALL);

        // 3.使用logger2子类输出(查看是否继承父类的属性)
        System.out.println("===================如下是继承父类配置的输出==========================");
        log(logger2);

        // 4.重写自定义logger2的日志配置(不想使用logger1父类继承的配置)
        // 注意: 如果没有取消默认配置,那么当前又设置了子类的属性,logs输出会输出2次重复的,因为继承父类输出1次,子类自己输出1次
        logger2.setUseParentHandlers(false);
        ConsoleHandler consoleHandler2 = new ConsoleHandler();
        consoleHandler.setLevel(Level.WARNING);
        logger2.addHandler(consoleHandler2);
        logger2.setLevel(Level.WARNING);

        // 4.使用logger2子类输出(查看是否继承父类的属性)
        System.out.println("===================如下是没有继承父类配置的输出==========================");
        log(logger2);
    }

    private static void log(Logger logger2) {
        logger2.severe("severe");
        logger2.warning("warning");
        logger2.info("info");
        logger2.config("config");
        logger2.fine("fine");
        logger2.finer("finer");
        logger2.finest("finest");
    }
}

// 输出内容
===================如下是继承父类配置的输出==========================
326, 2022 1:06:48 下午 Test log
严重: severe
326, 2022 1:06:48 下午 Test log
警告: warning
326, 2022 1:06:48 下午 Test log
信息: info
326, 2022 1:06:48 下午 Test log
配置: config
326, 2022 1:06:48 下午 Test log
详细: fine
326, 2022 1:06:48 下午 Test log
较详细: finer
326, 2022 1:06:48 下午 Test log
非常详细: finest
===================如下是没有继承父类配置的输出==========================
326, 2022 1:06:48 下午 Test log
严重: severe
326, 2022 1:06:48 下午 Test log
警告: warning

6、JUL自定义配置文件

以上所有配置相关操作都是以Java硬编码的形式进行的的,我们可以使用更加清晰,更加专业的一种做法,就是使用配置文件,如果我们没有自己添加配置文件,则会使用系统(JDK)默认的配置文件。默认配置文件路径$JAVAHOME\jre\lib\logging.properties,默认配置文件内容如下

############################################################
#  	Default Logging Configuration File
#
# You can use a different file by specifying a filename
# with the java.util.logging.config.file system property.  
# For example java -Djava.util.logging.config.file=myfile
############################################################

############################################################
#  	Global properties
############################################################

# "handlers" specifies a comma separated list of log Handler 
# classes.  These handlers will be installed during VM startup.
# Note that these classes must be on the system classpath.
# By default we only configure a ConsoleHandler, which will only
# show messages at the INFO and above levels.
handlers= java.util.logging.ConsoleHandler

# To also add the FileHandler, use the following line instead.
#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler

# Default global logging level.
# This specifies which kinds of events are logged across
# all loggers.  For any given facility this global level
# can be overriden by a facility specific level
# Note that the ConsoleHandler also has a separate level
# setting to limit messages printed to the console.
.level= INFO

############################################################
# Handler specific properties.
# Describes specific configuration info for Handlers.
############################################################

# default file output is in user's home directory.
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

# Limit the message that are printed on the console to INFO and above.
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

# Example to customize the SimpleFormatter output format 
# to print one-line log message like this:
#     <level>: <log message> [<date/time>]
#
# java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n

############################################################
# Facility specific properties.
# Provides extra control for each logger.
############################################################

# For example, set the com.xyz.foo logger to only log SEVERE
# messages:
com.xyz.foo.level = SEVERE

自定义配置文件:logging.properties。将该文件保存至resources目录下

# RootLogger顶级父元素指定的默认处理器,多个用逗号分隔
# handlers = java.util.logging.FileHandler,java.util.logging.ConsoleHandler
handlers = java.util.logging.ConsoleHandler
# RootLogger顶级父元素默认的日志级别为:ALL
.level = ALL

# 文件处理器属性设置
# 输出日志文件的路径
java.util.logging.FileHandler.pattern = ./logs/java%u.log
# 输出日志文件的限制(50000字节)
java.util.logging.FileHandler.limit = 50000
# 设置日志文件的数量
java.util.logging.FileHandler.count = 1
# 设置输出日志的格式,默认是以XML方式输出的
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
# 指定handler对象的日志级别,默认是INFO级别
java.util.logging.FileHandler.level = ALL
# 指定义追加的方式添加日志内容
java.util.logging.FileHandler.append = true

# 控制台处理器属性设置
# 控制台默认输出的格式
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# 指定handler对象的字符集编码方式
java.util.logging.ConsoleHandler.encoding = UTF-8
# 控制台输出默认的级别设置(默认是INFO)
java.util.logging.ConsoleHandler.level = ALL

# 指定日志消息格式
#设置日志格式
# java.util.logging.SimpleFormatter.format= %1$tc %2$s%n%4$s: %5$s%6$s%n
java.util.logging.SimpleFormatter.format = %4$s: %5$s [%1$tc]%n

# 自定义Logger使用:包名 + handlers/level
com.xyz.handlers = java.util.logging.ConsoleHandler,java.util.logging.FileHandler
com.xyz.level = SEVERE
# 必须关闭默认设置(只有关闭该配置,自定义设置才能生效,否则还会使用RootLogger的属性配置)
com.xyz.useParentHandlers = false

加载自定义配置文件代码:

package com.xyz;

import java.io.InputStream;
import java.util.logging.LogManager;
import java.util.logging.Logger;

public class Test {
    public static void main(String[] args) throws Exception {
        // 读取配置文件,通过类加载器
        InputStream ins = Test.class.getClassLoader().getResourceAsStream("logging.properties");
        // 创建LogManager
        LogManager logManager = LogManager.getLogManager();
        // 通过LogManager加载配置文件
        logManager.readConfiguration(ins);

        // 创建日志记录器(当前Logger使用的配置是RootLogger的)
        Logger logger = Logger.getLogger("com");
        logger.severe("severe");
        logger.warning("warning");
        logger.info("info");
        logger.config("config");
        logger.fine("fine");
        logger.finer("finer");
        logger.finest("finest");

        // 创建日志记录器(当前Logger使用的是自己自动配置的, Level配置为severe)
        Logger logger2 = Logger.getLogger("com.xyz");
        logger2.severe("severe test");
        logger2.warning("warning test");
        logger2.info("info test");
        logger2.config("config test");
        logger2.fine("fine test");
        logger2.finer("finer test");
        logger2.finest("finest test");
    }
}

// 输出内容
严重: severe [周六 326 14:57:57 CST 2022]
警告: warning [周六 326 14:57:58 CST 2022]
信息: info [周六 326 14:57:58 CST 2022]
配置: config [周六 326 14:57:58 CST 2022]
详细: fine [周六 326 14:57:58 CST 2022]
较详细: finer [周六 326 14:57:58 CST 2022]
非常详细: finest [周六 326 14:57:58 CST 2022]
严重: severe test [周六 326 14:57:58 CST 2022]

7、JUL自定义组件方式

这里通过继承Level、Handler、Formatter自定义Logger,日志存在文件中,且每个不同的loggerName存不同日志文件中。示例如下,解释请查看注释:

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.logging.*;

public class Test {
    /**
     * 自定义日志level
     */
    public static class SelfLevel extends Level {
        public static SelfLevel ERROR = new SelfLevel("ERROR", 910);

        /**
         * @param name  自定义级别名称
         * @param value 自定义级别的权重值
         */
        protected SelfLevel(String name, int value) {
            super(name, value);
        }
    }

    /**
     * 自定义日志格式formatter
     */
    public static class SelfFormatter extends Formatter {
        @Override
        public String format(LogRecord record) {
            return String.format("[%s] %s %s.%s: %s\r\n",
                    LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
                    record.getLevel().getName(),
                    record.getSourceClassName(),
                    record.getSourceMethodName(),
                    record.getMessage());
        }
    }

    /**
     * 自定义日志输出handler
     */
    public static class SelfHandle extends Handler {
        /**
         * 日志写入的位置
         */
        @Override
        public void publish(LogRecord record) {
            try {
                // 每一个不同的loggerName分别记在不同的日志文件中
                File file = new File(String.format("%s_%s.log", record.getLoggerName(),
                        LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))));
                if (!file.exists()) {
                    file.createNewFile();
                }
                FileWriter fw = new FileWriter(file, true);
                PrintWriter pw = new PrintWriter(fw);
                String log = String.format("[%s] %s %s.%s: %s\r\n",
                        LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
                        record.getLevel().getName(),
                        record.getSourceClassName(),
                        record.getSourceMethodName(),
                        record.getMessage());
                pw.write(log);
                pw.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

        /**
         * 刷新缓存区,保存数据
         */
        @Override
        public void flush() {
        }

        /**
         * 关闭
         */
        @Override
        public void close() throws SecurityException {
        }
    }

    public static void main(String[] args) {
        // 创建日志记录器
        Logger logger = Logger.getLogger(Test.class.getName());
        // 生成日志输出handler实例
        logger.addHandler(new SelfHandle());
        logger.info("info log");
        logger.severe("severe log");
        //使用自定义的logger level
        logger.log(SelfLevel.ERROR, "error log");
        
        System.out.println("------------------------");
        Logger log = Logger.getLogger("com.test");
        log.setUseParentHandlers(false);
        ConsoleHandler console = new ConsoleHandler();
        console.setFormatter(new SelfFormatter());
        log.addHandler(console);
        log.log(Level.INFO, "selfFormatter test");
    }
}

控制台输出内容如下:

3月 27, 2022 6:19:11 下午 Test main
信息: info log
3月 27, 2022 6:19:11 下午 Test main
严重: severe log
3月 27, 2022 6:19:11 下午 Test main
ERROR: error log
------------------------
[2022-03-27 18:19:11] INFO Test.main: selfFormatter test

在项目同级目录会生产文件Test_2022-03-27.log

[2022-03-27 18:19:11] INFO Test.main: info log
[2022-03-27 18:19:11] SEVERE Test.main: severe log
[2022-03-27 18:19:11] ERROR Test.main: error log

8、Logger private static final 为啥?

为什么将 Logger 对象声明为 private static final:

  1. private:是为了防止其他类使用当前类的日志对象
  2. static:是为了让每个类中的日志对象只生成一份,是属于类的,不是属于具体的实例的
  3. final:是为了避免日志对象在运行时被修改

9、参考文献 & 鸣谢

  • java日志:一、JUL使用【CSDN:小徐也要努力鸭】https://blog.csdn.net/a232884c/article/details/121103424
  • 日志技术之JUL入门【CSDN:StudyWinter】https://blog.csdn.net/Zhouzi_heng/article/details/107943477
  • java日志(二)–java官方日志jul的使用【CSDN:panda-star】https://blog.csdn.net/chinabestchina/article/details/104730196
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值