java日志:一、JUL使用
1 介绍
JUL全称java util Logging,是java原生的日志框架,使用时不需要引入三方类库,相对其他日志框架使用方便。
2 架构介绍
Application->Logger->Handler->Outside World
Loggers:被称为记录器,应用程序通过获取Logger对象,调用其API来发布日志信息。Logger通常是应用程序访问日志系统的入口程序。
Appenders:也被称为Handlers,每个Logger都会关联一组Handlers,Logger会将日志交给关联Handlers处理,由Handlers负责将日志做记录。Handlers在此是一个抽象,其具体的实现决定了日志记录的位置可以是控制台、文件、网络上的其他日志服务或操作系统日志等。
Layouts:也被称为Formatters,它负责对日志事件中的数据进行转换和格式化。Layouts决定了数据在一条日志记录中的最终形式。
Level:每条日志消息都有一个关联的日志级别。该级别粗略知道了日志消息的重要性和紧迫,可以将Level和Loggers,Appenders做关联以便于过滤消息。
Filter:过滤器,根据需要定制哪些信息会被记录,哪些信息会被放过。
3 简单使用
package com.base6;
import java.util.logging.Level;
import java.util.logging.Logger;
public class JUL {
public static void main(String[] args) {
JUL j=new JUL();
Class<? extends JUL> c=j.getClass();
System.out.println(c.getName());
//1.获取日志记录器对象
Logger logger=Logger.getLogger(c.getName());
//2.日志记录输出
logger.info("你好吗");
//通用方法进行日志记录
logger.log(Level.INFO,"我很好");
//通过占位符方式,输出变量值
String name ="xiaoxu";
Integer age = 34;
logger.log(Level.WARNING,"用户信息:{0},{1}",new Object[]{name,age});
}
}
4 日志级别
package com.base6;
import java.util.logging.Level;
import java.util.logging.Logger;
public class JUL {
public static void main(String[] args) {
JUL j=new JUL();
Class<? extends JUL> c=j.getClass();
System.out.println(c.getName());
//1.获取日志记录器对象
Logger logger=Logger.getLogger(c.getName());
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
}
}
jul默认的日志级别是info,低于该级别的不会输出。
5 自定义配置日志级别
配置文件日志打印时,需要注意,文件路径要存在,否则抛出NoSuchFileException
在当前路径下,新建一个文件夹logs,里面放置一个空的jul1.log文件
package com.base6;
import java.io.File;
import java.io.IOException;
import java.util.logging.*;
public class JUL {
public static void main(String[] args) throws IOException {
JUL j=new JUL();
Class<? extends JUL> c=j.getClass();
System.out.println(c.getName());
//1.获取日志记录器对象
Logger logger=Logger.getLogger(c.getName());
//关闭系统默认配置
logger.setUseParentHandlers(false);
//自定义配置日志级别
//创建ConsoleHandler 控制台输出
ConsoleHandler consoleHandler=new ConsoleHandler();
//创建简单格式转换对象
SimpleFormatter simpleFormatter=new SimpleFormatter();
//进行关联
consoleHandler.setFormatter(simpleFormatter);
logger.addHandler(consoleHandler);
//配置日志级别
logger.setLevel(Level.WARNING);
consoleHandler.setLevel(Level.WARNING);
//获取当前文件路径
File f=new File("");
System.out.println(f.getAbsolutePath());
System.out.println(System.getProperty("user.dir"));
//场景FileHandler 文件输出
FileHandler fileHandler=new FileHandler("./logs/jul1.log");
//进行关联
fileHandler.setFormatter(simpleFormatter);
logger.addHandler(fileHandler);
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
}
}
多次执行也只会有一次的记录:
6 Logger对象父子关系
package com.base6;
import java.io.File;
import java.io.IOException;
import java.util.logging.*;
public class JUL {
public static void main(String[] args) throws IOException {
JUL j=new JUL();
Class<? extends JUL> c=j.getClass();
//父子关系类似包结构,比如这里logger1是com.base6,logger2是com,子包会默认继承父包
//没有定义父包,比如这里的com,就默认继承 RootLogger
Logger logger1=Logger.getLogger(c.getName());
Logger logger2=Logger.getLogger("com");
//所有日志记录器的顶级父元素:LogManager$RootLogger,name:""
System.out.println(logger1.getParent()==logger2);
System.out.println("logger2 Parent:"+logger2.getParent()+",name:"+logger2.getParent().getName());
//关闭默认配置
logger2.setUseParentHandlers(false);
//设置logger2日志级别
ConsoleHandler consoleHandler=new ConsoleHandler();
SimpleFormatter simpleFormatter=new SimpleFormatter();
consoleHandler.setFormatter(simpleFormatter);
logger2.addHandler(consoleHandler);
//配置日志具体级别
logger2.setLevel(Level.WARNING);
consoleHandler.setLevel(Level.WARNING);
logger1.severe("severe");
logger1.warning("warning");
logger1.info("info");
logger1.config("config");
logger1.fine("fine");
logger1.finer("finer");
logger1.finest("finest");
}
}
7 JUL配置文件
查看getLogger源码:
进入getLogManager:
final void ensureLogManagerInitialized() {
final LogManager owner = this;
if (initializationDone || owner != manager) {
// we don't want to do this twice, and we don't want to do
// this on private manager instances.
return;
}
// Maybe another thread has called ensureLogManagerInitialized()
// before us and is still executing it. If so we will block until
// the log manager has finished initialized, then acquire the monitor,
// notice that initializationDone is now true and return.
// Otherwise - we have come here first! We will acquire the monitor,
// see that initializationDone is still false, and perform the
// initialization.
//
synchronized(this) {
// If initializedCalled is true it means that we're already in
// the process of initializing the LogManager in this thread.
// There has been a recursive call to ensureLogManagerInitialized().
final boolean isRecursiveInitialization = (initializedCalled == true);
assert initializedCalled || !initializationDone
: "Initialization can't be done if initialized has not been called!";
if (isRecursiveInitialization || initializationDone) {
// If isRecursiveInitialization is true it means that we're
// already in the process of initializing the LogManager in
// this thread. There has been a recursive call to
// ensureLogManagerInitialized(). We should not proceed as
// it would lead to infinite recursion.
//
// If initializationDone is true then it means the manager
// has finished initializing; just return: we're done.
return;
}
// Calling addLogger below will in turn call requiresDefaultLogger()
// which will call ensureLogManagerInitialized().
// We use initializedCalled to break the recursion.
initializedCalled = true;
try {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
assert rootLogger == null;
assert initializedCalled && !initializationDone;
// Read configuration.
owner.readPrimordialConfiguration();
// Create and retain Logger for the root of the namespace.
owner.rootLogger = owner.new RootLogger();
owner.addLogger(owner.rootLogger);
if (!owner.rootLogger.isLevelInitialized()) {
owner.rootLogger.setLevel(defaultLevel);
}
// Adding the global Logger.
// Do not call Logger.getGlobal() here as this might trigger
// subtle inter-dependency issues.
@SuppressWarnings("deprecation")
final Logger global = Logger.global;
// Make sure the global logger will be registered in the
// global manager
owner.addLogger(global);
return null;
}
});
} finally {
initializationDone = true;
}
}
}
打上断点,deBug刚才的代码:
点击下一步开始调试:
首先进去加载,当前有无配置类(这里没有):
然后判断有无自定义的配置文件(这里也没有):
没有,就读取java.home(也就是JDK安装的路径),找到jre,再找到lib目录:
############################################################
# 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
复制一份放在resources目录下:
自定义修改如下:
handlers= java.util.logging.ConsoleHandler
.level= WARNING
# 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 = WARNING
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
加载自定义配置文件:
package com.base6;
import org.testng.annotations.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.*;
public class JUL {
@Test
public void testLogProperties() throws IOException {
JUL j=new JUL();
Class<? extends JUL> c=j.getClass();
//读取配置文件,通过类加载器
InputStream i=JUL.class.getClassLoader().getResourceAsStream("logging.properties");
//创建LogManager
LogManager logManager=LogManager.getLogManager();
//通过LogManager加载配置文件
logManager.readConfiguration(i);
//创建日志记录器
Logger l=Logger.getLogger(c.getName());
l.severe("severe");
l.warning("warning");
l.info("info");
l.config("config");
l.fine("fine");
l.finer("finer");
l.finest("finest");
}
}
再次执行,效果如下:
#RootLogger 顶级父元素指定的默认处理器为:ConsoleHandler
handlers= java.util.logging.ConsoleHandler
#RootLogger 顶级父元素默认的日志级别为:WARNING
.level= WARNING
java.util.logging.FileHandler.pattern = ./logs/java%u.log
java.util.logging.FileHandler.limit = 50000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
#向控制台输出的 handler对象
#指定 handler 对象的日志级别
java.util.logging.ConsoleHandler.level = WARNING
#指定handler对象的日志消息格式对象
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
全局搜索ConsoleHandler的代码:ctrl+shift+A
修改字符集如下:
#向控制台输出的 handler对象
#指定 handler 对象的日志级别
java.util.logging.ConsoleHandler.level = WARNING
#指定handler对象的日志消息格式对象
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
#指定handler对象的字符集
java.util.logging.ConsoleHandler.encoding = UTF-8
#指定日志消息格式
java.util.logging.SimpleFormatter.format="%4$s: %5$s [%1$tc]%n"
日志文件的追加:
#RootLogger 顶级父元素指定的默认处理器为:ConsoleHandler
handlers= java.util.logging.ConsoleHandler,java.util.logging.FileHandler
#RootLogger 顶级父元素默认的日志级别为:WARNING
.level= WARNING
#向日志文件输出的handler对象
#指定日志文件路径
java.util.logging.FileHandler.pattern = ./logs/java%u.log
#指定文件内容大小
java.util.logging.FileHandler.limit = 50000
#指定日志文件数量
java.util.logging.FileHandler.count = 1
#指定handler对象日志消息格式对象
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
#java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
#指定以追加方式添加日志内容
java.util.logging.FileHandler.append = true
#向控制台输出的 handler对象
#指定 handler 对象的日志级别
java.util.logging.ConsoleHandler.level = WARNING
#指定handler对象的日志消息格式对象
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
#指定handler对象的字符集
java.util.logging.ConsoleHandler.encoding = UTF-8
#指定日志消息格式
java.util.logging.SimpleFormatter.format="%4$s: %5$s [%1$tc]%n"
重新执行:
8 自定义Loggers
#RootLogger 顶级父元素指定的默认处理器为:ConsoleHandler
handlers= java.util.logging.ConsoleHandler,java.util.logging.FileHandler
#RootLogger 顶级父元素默认的日志级别为:WARNING
.level= WARNING
#自定义Logger使用:包名+handlers
com.base6.handlers = java.util.logging.ConsoleHandler
com.base6.level = SEVERE
#关闭默认配置
com.base6.useParentHandlers = false
#向日志文件输出的handler对象
#指定日志文件路径
java.util.logging.FileHandler.pattern = ./logs/java%u.log
#指定文件内容大小
java.util.logging.FileHandler.limit = 50000
#指定日志文件数量
java.util.logging.FileHandler.count = 1
#指定handler对象日志消息格式对象
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
#java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
#指定以追加方式添加日志内容
java.util.logging.FileHandler.append = true
#向控制台输出的 handler对象
#指定 handler 对象的日志级别
java.util.logging.ConsoleHandler.level = WARNING
#指定handler对象的日志消息格式对象
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
#指定handler对象的字符集
java.util.logging.ConsoleHandler.encoding = UTF-8
#指定日志消息格式
java.util.logging.SimpleFormatter.format="%4$s: %5$s [%1$tc]%n"
package com.base6;
import org.testng.annotations.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.*;
public class JUL {
@Test
public void testLogProperties() throws IOException {
JUL j=new JUL();
Class<? extends JUL> c=j.getClass();
//读取配置文件,通过类加载器
InputStream i=JUL.class.getClassLoader().getResourceAsStream("logging.properties");
//创建LogManager
LogManager logManager=LogManager.getLogManager();
//通过LogManager加载配置文件
logManager.readConfiguration(i);
//创建日志记录器
Logger l=Logger.getLogger(c.getName());
l.severe("severe");
l.warning("warning");
l.info("info");
l.config("config");
l.fine("fine");
l.finer("finer");
l.finest("finest");
Logger l2=Logger.getLogger("test");
l2.severe("severe test");
l2.warning("warning test");
l2.info("info test");
}
}
9 日志原理解析
1 初始化LogManager
1.1 LogManager加载logging.properties配置
1.2 添加Logger到LogManager
2 从单例LogManager获取Logger
3 设置级别Level,并指定日志记录LogRecord
4 Filter提供了日志级别之外更细粒度的控制
5 Handler是用来处理日志输出位置
6 Formatter用来格式化LogRecord