本文章中涉及到的所有代码都已经上传到gitee仓库中:
Hello jul!
不需要引入任何依赖,直接上DEMO:
java
package com.matio.log.helloworld;
import java.util.logging.Logger;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("This is out!");
System.err.println("This is err!");
Logger.getLogger("X").info("Hello world!");
}
}
打印结果如下:
看到这样的打印结果,不知道大家是不是跟笔者一样好奇这样几个问题:
1.为什么打印格式长这样?能不能自己定制?
2.为什么使用System.err打印?
jul默认的配置文件是 ${java.home}/lib/logging.properties ,对应笔者电脑上的C:\Program Files\Java\jdk1.8.0_271\jre\lib\logging.properties文件
jul各个组件详解
- Logger:记录器。应用程序通过获取 Logger 对象,调用其 API 来发布日志信息。Logger 对象通常是应用程序访问日志系统的入口程序;
- Level:每条日志消息都有一个关联的日志级别。该级别粗略指导了日志消息的重要性和紧迫;
- Formatter:它负责对日志事件中的数据进行转换和格式化。Formatter决定了数据在一条日志记录中的最终形式;
- Handler:每个 Logger都可以关联n个Handler。Logger会将日志交给关联的所有Handler处理,由它们负责输出日志,其具体的实现决定了日志记录的位置可以是控制台、文件等;
- Filter:过滤器。根据需要定制哪些消息会被记录,哪些消息会被放过。
Level
Level 类定义了一组可用来控制日志输出的标准日志级别。日志 Level 对象是有序的,并且是通过有序的整数来指定。在给定的级别上启用日志记录也就启用了所有较高级别的日志记录。
Jul 默认级别是 info,只要日志级别高于 info,都会输出
日志级别从高至低
- OFF 可用于关闭日志记录的特殊级别
- SEVERE 指示严重失败的消息级别 (highest value)
- WARNING 指示潜在问题的消息级别
- INFO 报告消息的消息级别
- CONFIG 用于静态配置消息的消息级别
- FINE 提供跟踪信息的消息级别
- FINER 指示一条相当详细的跟踪消息
- FINEST 指示一条最详细的跟踪消息 (lowest value)
- ALL 记录所有消息
如果是:off,那么就是关闭了日志级别输出;
如果是:all,那么就是开启了日志级别输出。
jul默认是没有提供ERROR级别的,当然我们可以自己定义
自定义Level
java
package com.matio.log.level;
import java.util.logging.Level;
import java.util.logging.Logger;
public class CusLevelTest {
private static final ErrorLevel ERROR = new ErrorLevel();
public static void main(String[] args) {
Logger logger = Logger.getLogger(CusLevelTest.class.getName());
logger.log(ERROR, "123");
logger.info("info");
}
private static class ErrorLevel extends Level {
protected ErrorLevel() {
super("ERROR", 5000, "com.matio.log.level.logging");
}
}
}
package com.matio.log.level;
import java.util.ListResourceBundle;
public final class logging extends ListResourceBundle {
public logging() {
}
protected final Object[][] getContents() {
return new Object[][]{{"ERROR", "错误XXX"}};
}
}
打印结果如下:
js
三月 14, 2024 11:15:23 上午 com.matio.log.level.CusLevelTest main
错误XXX: 123
三月 14, 2024 11:15:23 上午 com.matio.log.level.CusLevelTest main
信息: info
Formatter
按照指定的格式去格式化我们的日志。jul默认提供了string和xml两种格式的输出
一般来说,每个日志记录 Handler 都有关联的 Formatter。Formatter 接受一个 LogRecord参数,并将它转换为一个字符串,这个字符串就是我们最终要打印的数据。
有些 formatter(如 XMLFormatter)需要围绕一组格式化记录来包装头部和尾部字符串。可以使用 getHeader 和 getTail 方法来获得这些字符串
logger.info("测试日志内容");
SimpleFormatter
jul默认使用这种formatter进行日志打印
java
package com.matio.log.formatter;
import java.util.logging.ConsoleHandler;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
public class StringFormatterTest {
public static void main(String[] args) {
Logger logger = Logger.getLogger(StringFormatterTest.class.getName());
logger.setUseParentHandlers(false);
// 将日志输出到控制台上
ConsoleHandler handler = new ConsoleHandler();
handler.setFormatter(new SimpleFormatter());
logger.addHandler(handler);
logger.info("test SimpleFormatter");
}
}
打印结果如下
java
三月 07, 2024 2:11:27 下午 com.matio.log.formatter.StringFormatterTest main
信息: test SimpleFormatter
不知道大家有没有想过这样的一个问题:为什么打印结果的格式是这个样子的?能不能自定义格式?
其实在SimpleFormatter类中一个静态属性format,这个就来定义日志按照何种格式打印的
java
public class SimpleFormatter extends Formatter {
// format string for printing the log record
private static final String format = LoggingSupport.getSimpleFormat();
}
static String getSimpleFormat(boolean var0) {
String var1 = (String)AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("java.util.logging.SimpleFormatter.format");
}
});
if (var0 && proxy != null && var1 == null) {
var1 = proxy.getProperty("java.util.logging.SimpleFormatter.format");
}
if (var1 != null) {
try {
String.format(var1, new Date(), "", "", "", "", "");
} catch (IllegalArgumentException var3) {
var1 = "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n";
}
} else {
var1 = "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n";
}
return var1;
}
可以看出java提供了java.util.logging.SimpleFormatter.format系统属性帮助用户自定义格式输出,:
System.setProperty("java.util.logging.SimpleFormatter.format", "时间:%s 类方法名:%s 日志名称:%s 日志级别:%s 日志内容:%s\n");
注意:
上面一行代码会影响所有的SimpleFormatter,因为SimpleFormatter.format是个静态属性。当然我们可以定义自己的Formatter避免受到影响
XMLFormatter
顾名思义,就是将日志内容按照xml格式输出,使用极少。往往打印一行日志时会附加很多额外属性信息
handler.setFormatter(new XMLFormatter());
打印结果如下
xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
<date>2024-03-07T14:11:48</date>
<millis>1709791908177</millis>
<sequence>0</sequence>
<logger>com.matio.log.formatter.StringFormatterTest</logger>
<level>INFO</level>
<class>com.matio.log.formatter.XmlFormatterTest</class>
<method>main</method>
<thread>1</thread>
<message>test XMLFormatter</message>
</record>
自定义Formatter
自定义一个Formatter去继承jul中的Formatter,然后设置给目标handler即可
java
package com.matio.log.formatter;
import java.util.logging.*;
public class CusFormatterTest {
public static void main(String[] args) {
Logger logger = Logger.getLogger(CusFormatterTest.class.getName());
logger.setUseParentHandlers(false);
// 将日志输出到控制台上
ConsoleHandler handler = new ConsoleHandler();
handler.setFormatter(new CusFormatter());
logger.addHandler(handler);
logger.info("test CusFormatter");
}
private static class CusFormatter extends Formatter {
@Override
public String format(LogRecord record) {
return record.getLevel().getName() + " : " + record.getMessage();
}
}
}
打印结果如下
java
INFO : test CusFormatter
Handler
Handler
对象从 Logger
中获取日志信息,并将这些信息导出。例如,它可将这些信息写入控制台或文件中,也可以将这些信息发送到网络日志服务中,或将其转发到操作系统日志中。
每一个logger可以通过 addHandler(Handler)
添加多个Handler对象,每一个Handler对会被顺序执行。另外可通过执行 setLevel(Level.OFF)
来禁用 Handler
,并可通过执行适当级别的 setLevel
来重新启用。
-Djava.util.logging.config.file=myfile
见静态代码块,主要目的就是初始化一个单例的LogManager对象
java
// The global LogManager object
private static final LogManager manager;
static {
manager = AccessController.doPrivileged(new PrivilegedAction<LogManager>() {
@Override
public LogManager run() {
LogManager mgr = null;
String cname = System.getProperty("java.util.logging.manager");
if (cname != null) {
Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(cname);
mgr = (LogManager) clz.newInstance();
}
if (mgr == null) {
mgr = new LogManager();
}
return mgr;
}
});
}
protected LogManager() {
this(checkSubclassPermissions());
}
private LogManager(Void checked) {
// Add a shutdown hook to close the global handlers.
try {
Runtime.getRuntime().addShutdownHook(new Cleaner());
} catch (IllegalStateException e) {
// If the VM is already shutting down,
// We do not need to register shutdownHook.
}
}
扩展点:自定义LogManager
从以上代码中知道,我们可以通过自定义一个LogManager
java
System.setProperty("java.util.logging.manager", "com.matio.log.logmanager.CustomLogManager");
LogManager logManager = LogManager.getLogManager();
// com.matio.log.logmanager.CustomLogManager@7f31245a
System.out.println(logManager);
java
package com.matio.log.logmanager;
import java.util.logging.LogManager;
public class CustomLogManager extends LogManager {
}
ConsoleHandler
将日志内容标准错误输出到控制台上,就是类似这种
java
System.err.println("三月 08, 2024 2:04:27 下午 com.matio.log.handler.ConsoleHandlerTest main");
System.err.println("信息: console handler!");
上下两个代码输出的内容一致
java
package com.matio.log.handler;
import java.io.UnsupportedEncodingException;
import java.util.logging.*;
public class ConsoleHandlerTest {
public static void main(String[] args) throws UnsupportedEncodingException {
Logger logger = Logger.getLogger(ConsoleHandlerTest.class.getName());
logger.setUseParentHandlers(false);
ConsoleHandler handler = new ConsoleHandler();
handler.setFormatter(new SimpleFormatter());
// handler.setLevel(Level.ALL);
// handler.setEncoding("UTF-8");
// 可以给每个handler设置一个ErrorManager,用来处理日志记录期间处理程序上发生的任何错误。
// 在处理日志记录输出时,如果处理程序遇到问题,那么处理程序不应该将异常返回
// 给日志记录调用的发出者(不太可能感兴趣),而应该调用其相关的ErrorManager。
// handler.setErrorManager(new ErrorManager());
handler.setFilter(null);
logger.addHandler(handler);
logger.info("console handler!");
}
}
支持以下配置
java
private void configure() {
LogManager manager = LogManager.getLogManager();
String cname = getClass().getName();
setLevel(manager.getLevelProperty(cname +".level", Level.INFO));
setFilter(manager.getFilterProperty(cname +".filter", null));
setFormatter(manager.getFormatterProperty(cname +".formatter", new SimpleFormatter()));
setEncoding(manager.getStringProperty(cname +".encoding", null));
}
- java.util.logging.ConsoleHandler.level 为
Handler
指定默认的级别(默认为INFO
)。 - java.util.logging.ConsoleHandler.filter 指定要使用的
Filter
类的名称(默认为null)。 - java.util.logging.ConsoleHandler.formatter 指定要使用的
Formatter
类的名称(默认为java.util.logging.SimpleFormatter
)。 - java.util.logging.ConsoleHandler.encoding 指定要使用的字符集编码的名称(默认为使用默认平台的编码)。
SocketHandler
开启一个socket客户端,将日志发送给指定的host和port
示例代码:
java
package com.matio.log.handler;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import java.util.logging.SocketHandler;
public class SocketHandlerTest {
public static void main(String[] args) throws InterruptedException {
// 模拟启动一个socketserver准备接受日志
new Thread(SocketHandlerTest::openServer).start();
// 等待socketserver启动完成
Thread.sleep(3000);
Logger logger = Logger.getLogger(SocketHandlerTest.class.getName());
logger.setUseParentHandlers(false);
// 把日志内容通过socket发送给远端
SocketHandler handler = null;
try {
handler = new SocketHandler("localhost", 12345);
handler.setFormatter(new SimpleFormatter());
} catch (IOException e) {
throw new RuntimeException(e);
}
handler.setLevel(Level.ALL);
logger.addHandler(handler);
logger.info("第一条日志内容");
logger.info("第二条日志内容");
}
// 使用文心一言写的demo
private static void openServer() {
int portNumber = 12345;
try (
ServerSocket serverSocket = new ServerSocket(portNumber);
Socket clientSocket = serverSocket.accept();
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
) {
String inputLine;
while ((inputLine = in.readLine()) != null) {
System.out.println("Received: " + inputLine);
if ("exit".equalsIgnoreCase(inputLine)) {
break;
}
out.println("Welcome to the server!");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
打印结果如下:
js
Received: 三月 12, 2024 1:52:04 下午 com.matio.log.handler.SocketHandlerTest main
Received: 信息: 第一条日志内容
Received: 三月 12, 2024 1:52:04 下午 com.matio.log.handler.SocketHandlerTest main
Received: 信息: 第二条日志内容
支持以下配置
java
private void configure() {
LogManager manager = LogManager.getLogManager();
String cname = getClass().getName();
setLevel(manager.getLevelProperty(cname +".level", Level.ALL));
setFilter(manager.getFilterProperty(cname +".filter", null));
setFormatter(manager.getFormatterProperty(cname +".formatter", new XMLFormatter()));
setEncoding(manager.getStringProperty(cname +".encoding", null));
port = manager.getIntProperty(cname + ".port", 0);
host = manager.getStringProperty(cname + ".host", null);
}
- java.util.logging.SocketHandler.level 指定该
Handler
的默认日志输出级别(默认值为Level.ALL
)。 - java.util.logging.SocketHandler.filter 指定要使用的
Filter
类的名称(默认值非Filter
)。 - java.util.logging.SocketHandler.formatter 指定要使用的
Formatter
(默认值为java.util.logging.XMLFormatter
)。 - java.util.logging.SocketHandler.encoding 要使用的字符集编码的名称(默认值为默认平台编码)。
- java.util.logging.SocketHandler.host 指定要连接到的目标主机名(无默认值)。
- java.util.logging.SocketHandler.port 指定要使用的目标 TCP 端口(无默认值)。
FileHandler
FileHandler
可以写入指定的文件,也可以写入指定n个轮换文件集合。
对于循环文件集合而言,到达每个文件的给定大小限制后,就关闭该文件,将其轮换出去,并打开新的文件。通过在基本文件名中添加 "0"、"1"、"2" 等来依次命名旧文件。
默认情况下,IO 库中启用了缓冲,但当缓冲完成时,每个日志记录都要被刷新
示例代码:
java
package com.matio.log.handler;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
public class FileHandlerTest {
public static void main(String[] args) throws IOException {
Logger logger = Logger.getLogger(FileHandlerTest.class.getName());
logger.setUseParentHandlers(false);
// 将日志文件保存在D:/test/目录下
// 限制单个日志文件大小不超过100M,且最多产生200个日志文件
FileHandler fileHandler = new FileHandler("D:/test/log%g-%u.log", 100 * 1024 * 1024, 200);
fileHandler.setFormatter(new SimpleFormatter());
fileHandler.setLevel(Level.ALL);
fileHandler.setEncoding("UTF-8");
// 打印出我们的日志实际保存在哪个目录
printLogPath(fileHandler);
logger.addHandler(fileHandler);
logger.info("test FileHandler");
}
// 打印出我们的日志实际保存在哪个目录
private static void printLogPath(FileHandler fileHandler) {
System.out.println(System.getProperty("user.home"));
System.out.println(System.getProperty("java.io.tmpdir"));
try {
Method method = FileHandler.class.getDeclaredMethod("generate", String.class, int.class, int.class);
method.setAccessible(true);
File file = (File) method.invoke(fileHandler, "D:/test/log%g-%u.log", 1, 2);
System.out.println("日志保存位置:" + file.getAbsolutePath());
} catch (Exception e) {
e.printStackTrace();
}
}
}
支持以下配置
- java.util.logging.FileHandler.level 为
Handler
指定默认的级别(默认为Level.ALL
)。 - java.util.logging.FileHandler.filter 指定要使用的
Filter
类的名称(默认为null)。 - java.util.logging.FileHandler.formatter 指定要使用的 Formatter 类的名称(默认为
java.util.logging.XMLFormatter
)。 - java.util.logging.FileHandler.encoding 指定要使用的字符集编码的名称(默认使用默认的平台编码)。
- java.util.logging.FileHandler.limit 指定要写入到任意文件的近似最大量(以字节为单位)。如果该数为 0,则没有限制(默认为无限制)。
- java.util.logging.FileHandler.count 指定有多少输出文件参与循环(默认为 1)。
- java.util.logging.FileHandler.pattern 为生成的输出文件名称指定一个模式。有关细节请参见以下内容(默认为 "%h/java%u.log")。
- java.util.logging.FileHandler.append 指定是否应该将 FileHandler 追加到任何现有文件上(默认为 false)。
pattern具体由包括以下特殊字符串组成(可以参考java.util.logging.FileHandler#generate
):
- "/" 本地路径名分隔符
- "%t" 系统临时目录
- "%h" "user.home" 系统属性的值
- "%g" 区分循环日志的生成号
- "%u" 解决冲突的惟一号码
- "%%" 转换为单个百分数符号"%"
如果未指定 "%g" 字段,并且文件计数大于 1,那么生成号将被添加到所生成文件名末尾的小数点后面。
例如,文件计数为 2 的 "%t/java%g.log" 模式通常导致在 Solaris 系统中将日志文件写入 /var/tmp/java0.log 和 /var/tmp/java1.log,而在 Windows 95 中,则将其写入 C:\TEMP\java0.log 和 C:\TEMP\java1.log。
按照 0、1、2 等的序列安排生成号。
通常,将惟一字段 "%u" 设置为 0。但是如果 FileHandler
试图打开文件名并查找当前被另一个进程使用的文件,则增加惟一的字段号并再次重试。重复此操作直到 FileHandler
找到当前没有被使用的文件名。如果有冲突并且没有指定 "%u" 字段,则将该字段添加到文件名末尾的小数点后(它将位于所有自动添加的生成号后面)。
因此,如果三个进程都试图将日志记录到 fred%u.%g.txt,那么它们可能将 fred0.0.txt、fred1.0.txt、fred2.0.txt 作为其循环序列中的首个文件而结束。
注意,使用本地磁盘文件系统时,使用惟一的 id 以避免冲突是系统可靠运行的惟一保证。
比如pattern=D:/test/log%g-%u.log就是将日志保存在D:/test/目录下
保存日志的目录必须要提前创建好,否则会抛出以下异常:
java
Exception in thread "main" java.nio.file.NoSuchFileException: D:\test\log0-0.log.lck
at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:79)
at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97)
at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:102)
at sun.nio.fs.WindowsFileSystemProvider.newFileChannel(WindowsFileSystemProvider.java:115)
at java.nio.channels.FileChannel.open(FileChannel.java:287)
at java.nio.channels.FileChannel.open(FileChannel.java:335)
at java.util.logging.FileHandler.openFiles(FileHandler.java:478)
at java.util.logging.FileHandler.<init>(FileHandler.java:310)
at com.matio.log.handler.FileHandlerTest.main(FileHandlerTest.java:18)
MemoryHandler
底层维护了一个默认长度1000的环形数组用来缓冲处理日志。当用户调用logger.info("test");输出日志时都会生成一个LogRecord存放在这个环形数组上。当满足一定条件时才会遍历这个环形数组然后调用真正的Handler去输出日志。
如果数组满了会覆盖旧的日志
当满足以下三种条件中的任意一个时,会触发缓冲区的 push 操作
- 传入的
LogRecord
类型大于预先定义的pushLevel
级别。 - 外部类显式地调用
push
方法。 - 如果记录符合所需的某些标准,则子类重写
log
方法,并扫描每个传入的LogRecord
,调用push
。
java
public synchronized void publish(LogRecord record) {
if (!isLoggable(record)) {
return;
}
int ix = (start+count)%buffer.length;
buffer[ix] = record;
if (count < buffer.length) {
count++;
} else {
start++;
start %= buffer.length;
}
if (record.getLevel().intValue() >= pushLevel.intValue()) {
push();
}
}
public synchronized void push() {
for (int i = 0; i < count; i++) {
int ix = (start+i)%buffer.length;
LogRecord record = buffer[ix];
target.publish(record);
}
// Empty the buffer.
start = 0;
count = 0;
}
支持以下配置:
- java.util.logging.MemoryHandler.level 指定
Handler
的级别(默认为Level.ALL
)。 - java.util.logging.MemoryHandler.filter 指定要使用的
Filter
类的名称(默认为无null)。 - java.util.logging.MemoryHandler.size 定义缓冲区的大小(默认为 1000)。
- java.util.logging.MemoryHandler.push 定义
pushLevel
(默认为level.SEVERE
)。 - java.util.logging.MemoryHandler.target 指定目标
Handler
类的名称(无默认值)。
java
private void configure() {
LogManager manager = LogManager.getLogManager();
String cname = getClass().getName();
pushLevel = manager.getLevelProperty(cname +".push", Level.SEVERE);
size = manager.getIntProperty(cname + ".size", 1000);
if (size <= 0) {
size = 1000;
}
setLevel(manager.getLevelProperty(cname +".level", Level.ALL));
setFilter(manager.getFilterProperty(cname +".filter", null));
setFormatter(manager.getFormatterProperty(cname +".formatter", new SimpleFormatter()));
}
示例代码
java
package com.matio.log.handler;
import java.io.UnsupportedEncodingException;
import java.util.logging.*;
public class MemoryHandlerTest {
public static void main(String[] args) throws UnsupportedEncodingException {
Logger logger = Logger.getLogger(MemoryHandlerTest.class.getName());
logger.setUseParentHandlers(false);
// memoryHandler中的target
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setLevel(Level.ALL);
consoleHandler.setFormatter(new SimpleFormatter());
MemoryHandler memoryHandler = new MemoryHandler(consoleHandler, 10, Level.WARNING);
memoryHandler.setLevel(Level.INFO);
logger.addHandler(memoryHandler);
// 这句并不会打印日志,因为日志还在缓冲区内
logger.info("warning msg");
// 主动刷新缓存,输出日志
memoryHandler.push();
}
}
自定义Handler
java
package com.matio.log.handler;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
public class CusHandlerTest {
public static void main(String[] args) {
Logger logger = Logger.getLogger(CusHandlerTest.class.getName());
logger.setUseParentHandlers(false);
logger.addHandler(new CusHandler());
logger.info("哈哈哈");
}
private static class CusHandler extends Handler {
@Override
public void publish(LogRecord record) {
System.out.println(record.getLevel().getName() + " : " + record.getMessage());
}
@Override
public void flush() {
}
@Override
public void close() throws SecurityException {
}
}
}
Filter
Filter 可用于为记录内容提供比记录级别所提供的更细粒度的控制。
每个 Logger 和 Handler 都可以关联一个过滤器。Logger 或 Handler 可以调用 isLoggable 方法来检查是否应该发布给定的 LogRecord。如果 isLoggable 返回 false,则丢弃 LogRecord。
接口定义如下
java
public interface Filter {
boolean isLoggable(LogRecord record);
}
示例代码
java
package com.matio.log.filter;
import java.io.IOException;
import java.util.logging.*;
/**
* 过滤器Filter可用于提供对所记录内容的精细控制,超出了日志级别提供的控制范围。
* 每个Logger和每个Handler都可以有一个与其关联的筛选器。Logger或Handler将调用isLogable方法来检查是否应该发布给定的LogRecord。
* 如果isLoggable返回false,则LogRecord将被丢弃。
*/
public class FilterTest {
public static void main(String[] args) throws IOException {
Logger logger = Logger.getLogger("FilterTest");
logger.setUseParentHandlers(false);
// 输出到控制台上
ConsoleHandler handler = new ConsoleHandler();
handler.setFormatter(new SimpleFormatter());
// 可以给每一个handler设置一个filter
handler.setFilter(new CusFilter1());
logger.addHandler(handler);
// 将日志文件保存在D:/test/目录下
FileHandler fileHandler = new FileHandler("D:/test/log%g-%u.log", 100 * 1024 * 1024, 200);
fileHandler.setFormatter(new SimpleFormatter());
fileHandler.setLevel(Level.ALL);
fileHandler.setFilter(new CusFilter2());
fileHandler.setEncoding("UTF-8");
// 可以给每一个logger设置一个filter
logger.setFilter(new CusFilter1());
logger.info("test filter");
}
private static class CusFilter1 implements Filter {
@Override
public boolean isLoggable(LogRecord record) {
return true;
}
}
private static class CusFilter2 implements Filter {
@Override
public boolean isLoggable(LogRecord record) {
return false;
}
}
}
LogManager
存在一个单一的全局 LogManager 对象,它可用于维护 Logger 和日志服务的一组共享状态
自定义LogManager
具体可以参考LogManager类中的静态代码块
示例代码:
java
package com.matio.log.logmanager;
import java.util.logging.LogManager;
import java.util.logging.Logger;
public class LogManagerTest0 {
public static void main(String[] args) {
// 因为LogManager在类加载的时候就会生成唯一实例
// 所以需要保证这行代码在LogManager类被加载前执行
System.setProperty("java.util.logging.manager", "com.matio.log.logmanager.CustomLogManager");
System.out.println(LogManager.getLogManager());
Logger logger = Logger.getLogger(LogManagerTest0.class.getName());
logger.info("info");
}
}
package com.matio.log.logmanager;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.LogManager;
import java.util.logging.Logger;
// 自定义LogManager,不使用原生的了
public class CustomLogManager extends LogManager {
public CustomLogManager() {
System.out.println("CustomLogManager is loaded!");
}
@Override
public void readConfiguration() throws IOException, SecurityException {
// java.util.logging.config.class
// java.util.logging.config.file
super.readConfiguration();
// todo 我们可以加载自定义配置文件
}
@Override
public boolean addLogger(Logger logger) {
System.out.println("注册 : " + logger.getName());
// todo 我们可以配置任意的logger的属性,比如:
/*logger.setLevel(null);
logger.setResourceBundle(null);
logger.setUseParentHandlers(false);
logger.setFilter(null);
logger.setParent(null);*/
return super.addLogger(logger);
}
@Override
public void readConfiguration(InputStream ins) throws IOException, SecurityException {
super.readConfiguration(ins);
}
}
另外,LogManager 使用两个可选的允许更好地控制初始配置读取的系统属性:
- java.util.logging.config.class
- java.util.logging.config.file
(其实jul默认的配置文件是 ${java.home}/lib/logging.properties ,对应笔者电脑上的C:\Program Files\Java\jdk1.8.0_271\jre\lib\logging.properties文件)
如果设置了 "java.util.logging.config.class " 属性,则会把属性值当作类名。给定的类将会被加载,并会实例化一个对象,该对象的构造方法负责读取初始配置。(此对象可以使用其他系统属性来控制自己的配置。)此备用配置类可使用 readConfiguration(InputStream) 来定义 LogManager 中的属性。
如果未设置 "java.util.logging.config.class " 属性,则会使用 "java.util.logging.config.file" 系统属性来指定一个properties文件。从此文件读取初始日志配置。
如果这两个属性都没有定义,则如上所述,LogManager 将默认从 ${java.home}/lib/logging.properties 中读取其初始配置。具体可以参考
java.util.logging.LogManager#readConfiguration()
这里附带上 ${java.home}/lib/logging.properties 文件内容:
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
全局日志属性可以包括:
- handlers:该属性为 handler 类定义类名的空白分隔列表,以便作为处理程序在根 Logger(该 Logger 名为 "")中加载和注册。每个类名必须用于具有默认构造方法的 Handler 类。
- ${loggerName}.handlers :该属性为 handler 类定义空白分隔或逗号分隔的列表,以便作为处理程序加载和注册到指定的 logger。每个类名必须用于一个具有默认构造方法的 Handler 类。
- ${loggerName}.useParentHandlers:该属性定义一个 boolean 值。默认情况下,每个 logger 除了自己处理日志消息外,还可能调用其父级来处理,这往往也会导致根 logger 来处理消息。将此属性设置为 false 时,需要为此 logger 配置 Handler,否则不传递任何消息。
- config:此属性允许运行任意配置代码。该属性定义类名的空白分隔的列表。为每个指定类创建新实例。每个类的默认构造方法都可以执行任意代码来更新日志配置,如设置 logger 级别、添加处理程序、添加过滤器,等等。
注意,在 LogManager 配置期间加载的所有类,其搜索顺序是先从系统类路径中搜索,然后才从用户类中搜索。这包括 LogManager 类、任何 config 类和任何 handler 类。
Logger 是按其圆点分隔的名称被组织到命名层次结构中的。因此,"a.b.c" 是 "a.b" 的子级,但 "a.b1" 和 a.b2" 属于同一级。
假定所有以 ".level" 结尾的名称的属性为 Logger 定义日志级别。因此,"foo.level" 就为名称为 "foo" 的 logger 定义了日志级别,进而为指定层次结构中它的所有子级也逐个定义了日志级别。日志级别是按其在属性文件中的定义顺序应用的。因此,树中子节点的级别设置应该迟于其父级设置。属性名 ".level" 可用于设置树的根级。
LogManager 对象上的所有方法都是多线程安全的。
java.util.logging.config.class
如果设置了 "java.util.logging.config.class" 属性,则会把属性值当作类名。给定的类将会被加载,并会实例化一个对象,该对象的构造方法负责读取初始配置。(此对象可以使用其他系统属性来控制自己的配置。)此备用配置类可使用 readConfiguration(InputStream)
来定义 LogManager 中的属性。 示例代码如下:
java
package com.matio.log.logmanager;
import java.util.logging.Logger;
public class LogManagerTest1 {
public static void main(String[] args) {
// 自定义配置文件
System.setProperty("java.util.logging.config.class", "com.matio.log.logmanager.JulConfig");
Logger logger = Logger.getLogger(LogManagerTest1.class.getName());
logger.info("info");
}
}
package com.matio.log.logmanager;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.logging.LogManager;
public class JulConfig {
public JulConfig() {
init();
}
private void init() {
String path = "E:\\WorkspaceIdea\\01src\\log\\matio-jul\\src\\main\\resources\\logging.properties";
System.out.println("init jul by " + path);
try {
FileInputStream fileInputStream = new FileInputStream(path);
LogManager.getLogManager().readConfiguration(fileInputStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
java.util.logging.config.file
如果未设置 "java.util.logging.config.class " 属性,则会使用 "java.util.logging.config.file" 系统属性来指定一个Properties文件。从此文件读取初始日志配置
java
package com.matio.log.logmanager;
import java.util.logging.Logger;
public class LogManagerTest2 {
public static void main(String[] args) {
// 指定jul的默认配置文件路径
System.setProperty("java.util.logging.config.file", "E:\\WorkspaceIdea\\01src\\log\\matio-jul\\src\\main\\resources\\logging.properties");
Logger logger = Logger.getLogger(LogManagerTest2.class.getName());
logger.info("info");
}
}