Java日志框架学习--JUL和Log4j--上
Posted 大忽悠爱忽悠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java日志框架学习--JUL和Log4j--上相关的知识,希望对你有一定的参考价值。
Java日志框架学习--JUL和Log4j--上
引言
日志框架
1.控制日志输出的内容和格式。
2.控制日志输出的位置。
3.日志文件相关的优化,如异步操作、归档、压缩…
4.日志系统的维护
5.面向接口开发 – 日志的门面
市面流行的日志框架
JUL java util logging
- Java原生日志框架,亲儿子
Log4j
- Apache的一个开源项目
Logback
- 由Log4j之父做的另一个开源项目
- 业界中称作log4j后浪
- 一个可靠、通用且灵活的java日志框架
Log4j2
- Log4j官方的第二个版本,各个方面都是与Logback及其相似
- 具有插件式结构、配置文件优化等特征
- Spring Boot1.4版本以后就不再支持log4j,所以第二个版本营运而生
JCL
SLF4j
日志门面和日志框架的区别
日志框架技术 JUL、Logback、Log4j、Log4j2
用来方便有效地记录日志信息
日志门面技术 JCL、SLF4j
为什么要使用日志门面技术:
每一种日志框架都有自己单独的API,要使用对应的框架就要使用对应的API,这就大大的增加了应用程序代码对于日志框架的耦合性。
我们使用了日志门面技术之后,对于应用程序来说,无论底层的日志框架如何改变,应用程序不需要修改任意一行代码,就可以直接上线了。
JUL
JUL简介
JUL全称 Java Util Logging,它是java原生的日志框架,使用时不需要另外引用第三方的类库,相对其他的框架使用方便,学习简单,主要是使用在小型应用中。
JUL组件介绍
Logger:被称为记录器,应用程序通过获取Logger对象,使用其API来发布日志信息。Logger通常被认为是访问日志系统的入口程序。
Handler:处理器,每个Logger都会关联一个或者是一组Handler,Logger会将日志交给关联的Handler去做处理,由Handler负责将日志做记录。Handler具体实现了日志的输出位置,比如可以输出到控制台或者是文件中等等。
Filter:过滤器,根据需要定制哪些信息会被记录,哪些信息会被略过。
Formatter:格式化组件,它负责对日志中的数据和信息进行转换和格式化,所以它决定了我们输出日志最终的形式。
Level:日志的输出级别,每条日志消息都有一个关联的级别。我们根据输出级别的设置,用来展现最终所呈现的日志信息。根据不同的需求,去设置不同的级别。
实际使用
public class JUITest
private final String NOW="当前时间: "+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"));
@Test
public void normalTest()
//获取Logger--传入当前类的全类名
Logger logger = Logger.getLogger(JUITest.class.getName());
//info级别输出日志
logger.info(NOW);
logger.log(Level.INFO,NOW);
//占位符传参--0和1必须指定,否则占位符不生效
logger.log(Level.INFO,"管理员姓名为: 0, 年龄为1",new Object[]"大忽悠",18);
//输出不同的日志级别--默认为info,因此只会输出比info级别高的日志信息,包括info
logger.severe("severe");
logger.warning("warning");
logger.info("info");
//------------默认输出上面三个-------------
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
@Test
public void changeLevelTest()
Logger logger = Logger.getLogger(JUITest.class.getName());
//将默认的日志打印方式关闭掉,这样日志打印方式就不会按照父logger默认的方式去进行操作
logger.setUseParentHandlers(false);
//使用控制台处理器
ConsoleHandler handler = new ConsoleHandler();
//创建日志格式化组件
SimpleFormatter formatter = new SimpleFormatter();
//设置处理器中日志输出格式
handler.setFormatter(formatter);
//在记录器中添加处理器
logger.addHandler(handler);
//设置日志级别
//此处必须将日志记录器和处理器的基本进行统一的设置,才会达到日志显示相应级别的效果
logger.setLevel(Level.ALL);
handler.setLevel(Level.ALL);
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
/**
* handler可以同时添加多个
*/
@Test
public void logToFile() throws IOException
Logger logger = Logger.getLogger(JUITest.class.getName());
logger.setUseParentHandlers(false);
ConsoleHandler consoleHandler = new ConsoleHandler();
FileHandler fileHandler = new FileHandler("test.log");
SimpleFormatter formatter = new SimpleFormatter();
consoleHandler.setFormatter(formatter);
fileHandler.setFormatter(formatter);
logger.addHandler(consoleHandler);
logger.addHandler(fileHandler);
logger.setLevel(Level.ALL);
consoleHandler.setLevel(Level.ALL);
fileHandler.setLevel(Level.INFO);
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
给出的是最后一个测试的结果
真正完成日志记录的源码如下,其实很简单:
public void log(LogRecord record)
if (!isLoggable(record.getLevel()))
return;
Filter theFilter = filter;
if (theFilter != null && !theFilter.isLoggable(record))
return;
// Post the LogRecord to all our Handlers, and then to
// our parents' handlers, all the way up the tree.
Logger logger = this;
while (logger != null)
final Handler[] loggerHandlers = isSystemLogger
? logger.accessCheckedHandlers()
: logger.getHandlers();
for (Handler handler : loggerHandlers)
//每个handler的publish方法,也会去判断当前日志级别,然后拿到格式化器,最终进行输出
handler.publish(record);
final boolean useParentHdls = isSystemLogger
? logger.useParentHandlers
: logger.getUseParentHandlers();
if (!useParentHdls)
break;
logger = isSystemLogger ? logger.parent : logger.getParent();
不同handler的相同抽象publish的核心逻辑
@Override
public synchronized void publish(LogRecord record)
if (!isLoggable(record))
return;
String msg;
try
msg = getFormatter().format(record);
catch (Exception ex)
// We don't want to throw an exception here, but we
// report the exception to any registered ErrorManager.
reportError(null, ex, ErrorManager.FORMAT_FAILURE);
return;
try
if (!doneHeader)
writer.write(getFormatter().getHead(this));
doneHeader = true;
writer.write(msg);
catch (Exception ex)
// We don't want to throw an exception here, but we
// report the exception to any registered ErrorManager.
reportError(null, ex, ErrorManager.WRITE_FAILURE);
writer可以是输出向控制台,也可以是文件。
private volatile Writer writer;
Logger之间的父子关系
@Test
public void testParentLogger()
//父亲是RootLogger,名称默认是一个空的字符串,RootLogger可以被称之为所有Logger对象的顶层Logger
Logger logger1 = Logger.getLogger("helper.com.logTest");
Logger logger2 = Logger.getLogger("helper.com.logTest.JUITest");
System.out.println("log1的父log引用为: "+logger1.getParent()+" ; 名称为: "+logger1.getName()+
" 父亲的名称为: "+logger1.getParent().getName());
System.out.println("log2的父log引用为: "+logger2.getParent()+" ; 名称为: "+logger2.getName()+
" 父亲的名称为: "+logger2.getParent().getName());
父亲的设置会影响到儿子,也可以按照包的角度来理解,我可以设置整个包的日志属性,也可以定制化包下某个类的日志属性
@Test
public void testParentLogger()
Logger logger1 = Logger.getLogger("helper.com.logTest");
logger1.setUseParentHandlers(false);
ConsoleHandler handler = new ConsoleHandler();
handler.setLevel(Level.ALL);
logger1.addHandler(handler);
logger1.setLevel(Level.ALL);
Logger logger2 = Logger.getLogger("helper.com.logTest.JUITest");
logger2.severe("severe");
logger2.warning("warning");
logger2.info("info");
logger2.fine("fine");
logger2.finer("finer");
logger2.finest("finest");
logger2默认为info级别,但是因为我们设置了它的父logger日志级别,所以这里按照父logger属性进行输出
底层通过一个保存的节点树,再创建每个logger的时候,通过节点树,找到其父节点
默认配置文件位置
我们上面都是硬编码方式完成的,但是大部分情况下,都是通过配置文件完成的
Logger.getLogger方法会调用到ensureLogManagerInitialized方法,默认配置文件的加载在该方法中完成
final void ensureLogManagerInitialized()
....
// Read configuration.--读取配置文件
owner.readPrimordialConfiguration();
...
private void readPrimordialConfiguration()
if (!readPrimordialConfiguration)
synchronized (this)
if (!readPrimordialConfiguration)
// If System.in/out/err are null, it's a good
// indication that we're still in the
// bootstrapping phase
if (System.out == null)
return;
readPrimordialConfiguration = true;
try
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>()
@Override
public Void run() throws Exception
//继续看
readConfiguration();
// Platform loggers begin to delegate to java.util.logging.Logger
sun.util.logging.PlatformLogger.redirectPlatformLoggers();
return null;
);
catch (Exception ex)
assert false : "Exception raised while reading logging configuration: " + ex;
public void readConfiguration() throws IOException, SecurityException
checkPermission();
// if a configuration class is specified, load it and use it.
String cname = System.getProperty("java.util.logging.config.class");
if (cname != null)
try
// Instantiate the named class. It is its constructor's
// responsibility to initialize the logging configuration, by
// calling readConfiguration(InputStream) with a suitable stream.
try
Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(cname);
clz.newInstance();
return;
catch (ClassNotFoundException ex)
Class<?> clz = Thread.currentThread().getContextClassLoader().loadClass(cname);
clz.newInstance();
return;
catch (Exception ex)
System.err.println("Logging configuration class \\"" + cname + "\\" failed");
System.err.println("" + ex);
// keep going and useful config file.
//查看是否指定了配置文件位置
String fname = System.getProperty("java.util.logging.config.file");
if (fname == null)
//如果没有指定,会采用默认的配置文件
fname = System.getProperty("java.home");
if (fname == null)
throw new Error("Can't find java.home ??");
File f = new File(fname, "lib");
f = new File(f, "logging.properties");
//默认配置文件为java_home目录下的lib目录下的logging.properties文件
fname = f.getCanonicalPath();
try (final InputStream in = new FileInputStream(fname))
final BufferedInputStream bin = new BufferedInputStream(in);
readConfiguration(bin);
默认配置文件如下:
#RootLogger使用的处理器
#如果想要添加其他的处理器,可以采用逗号分隔的形式,添加多个处理器
handlers= java.util.logging.ConsoleHandler
#默认RootLogger的日志级别
#全局日志级别
.level= INFO
#文件处理器属性设置
#输出日志文件路径设置
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
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
#也可以将日志级别设置到具体的某个包下
com.xyz.foo.level = SEVERE
读取自定义配置文件
@Test
public void testParentLogger() throws IOException
LogManager logManager = LogManager.getLogManager();
logManager.readConfiguration(new 以上是关于Java日志框架学习--JUL和Log4j--上的主要内容,如果未能解决你的问题,请参考以下文章
Java日志框架 -- 日志框架介绍日志门面技术JUL日志(JUL架构JUL入门示例JUL日志级别JUL日志的配置文件)