1. 日志框架
目前的日志框架有:
- jdk-logging
- log4j1
- log4j2
- logback
用于实现日志统一的框架有:
- commons-logging
- slf4j
1. jdk-logging
使用方法
1.Demo
package com.jeiker.demo.controller;
import java.util.logging.Logger;
/**
* @Author : xiao
* @Date : 17/3/15 下午7:47
*/
public class JdkTest {
private static final Logger logger = Logger.getLogger(JdkTest.class.getName());
public static void main(String[] args){
logger.info("jdk logging info: a msg");
}
}
简单分析
(可跳过)
1.创建LogManager
默认是java.util.logging.LogManager,但是也可以自定义。
源码如下(manager就是LogManager):
try {
cname = System.getProperty("java.util.logging.manager");
if (cname != null) {
try {
Class clz = ClassLoader.getSystemClassLoader().loadClass(cname);
manager = (LogManager) clz.newInstance();
} catch (ClassNotFoundException ex) {
Class clz = Thread.currentThread().getContextClassLoader().loadClass(cname);
manager = (LogManager) clz.newInstance();
}
}
} catch (Exception ex) {
System.err.println("Could not load Logmanager \"" + cname + "\"");
ex.printStackTrace();
} if (manager == null) {
manager = new LogManager();
}
2.加载配置文件
默认是jre目录下的lib/logging.properties文件,也可以自定义修改系统属性”java.util.logging.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");
fname = f.getCanonicalPath();
}
InputStream in = new FileInputStream(fname);
BufferedInputStream bin = new BufferedInputStream(in);
try {
readConfiguration(bin);
}catch (Exception ex){
}
3.创建Logger
创建Logger,并缓存起来,放置到一个Hashtable中,并把LogManager设置进新创建的logger中。
2. log4j1
使用方法
1.pom文件增加依赖
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
依赖的jar包:
- log4j-1.2.17.jar
2.配置文件 :log4j.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd">
<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
</layout>
</appender>
<root>
<appender-ref ref="STDOUT" />
</root>
</log4j:configuration>
3.Demo
package com.jeiker.demo.controller;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
/**
* @Author : xiao
* @Date : 17/3/15 下午7:29
*/
public class Log4jTest {
private static final Logger logger = LogManager.getLogger(Log4jTest.class);
public static void main(String[] args){
if(logger.isTraceEnabled()){
logger.debug("log4j trace message");
}
if(logger.isDebugEnabled()){
logger.debug("log4j debug message");
}
if(logger.isInfoEnabled()){
logger.debug("log4j info message");
}
}
}
简单分析
(可跳过)
简单的说明获取一个Logger的过程。
分三种情况来说明:
(一)没有指定配置文件路径
引发LogManager的类初始化
Logger.getLogger(Log4jTest.class)的源码如下:
static public Logger getLogger(Class clazz) { return LogManager.getLogger(clazz.getName()); }
初始化一个logger仓库Hierarchy
Hierarchy的源码如下:
public class Hierarchy implements LoggerRepository, RendererSupport, ThrowableRendererSupport { private LoggerFactory defaultFactory; Hashtable ht; Logger root; //其他略 }
- LoggerFactory defaultFactory: 就是创建Logger的工厂
- Hashtable ht:用来存放上述工厂创建的Logger
- Logger root:作为根Logger
在LogManager的类初始化的过程中默认寻找类路径下的配置文件
- 通过org.apache.log4j.helpers.Loader类来加载类路径下的配置文件:
Loader.getResource("log4j.xml"); Loader.getResource("log4j.properties")
- 优先选择xml配置文件
解析上述配置文件
如果是xml文件则org.apache.log4j.xml.DOMConfigurator类来解析
如果是properties文件,则使用org.apache.log4j.PropertyConfigurator来解析
不再详细说明解析过程,看下解析后的结果:
设置RootLogger的级别
对RootLogger添加一系列我们配置的appender(我们通过logger来输出日志,通过logger中的appender指明了日志的输出目的地)
获取Logger
使用logger仓库Hierarchy中内置的LoggerFactory工厂来创建Logger了,并缓存起来,同时将logger仓库Hierarchy设置进新创建的Logger中。
(二)手动加载不在路径下的配置文件
PropertyConfigurator.configure 执行时会去进行上述的配置文件解析。
源码如下:
public static void configure(java.net.URL configURL) {
new PropertyConfigurator().doConfigure(configURL, LogManager.getLoggerRepository());
}
仍然先会引发LogManager的类加载,创建出logger仓库Hierarchy,同时尝试加载类路径下的配置文件,此时没有则不进行解析,此时logger仓库Hierarchy中的RootLogger默认采用debug级别,没有appender而已。
然后解析配置文件,对上述logger仓库Hierarchy的RootLogger进行级别的设置,添加appender
此时再去调用Logger.getLogger,不会导致LogManager的类初始化(因为已经加载过了)
(三)配置文件在类路径下,而我们又手动使用PropertyConfigurator去加载
也就会造成2次加载解析配置文件,仅仅会造成覆盖而已(对于RootLogger进行从新设置级别,删除原有的appender,重新加载新的appender),所以多次加载解析配置文件以最后一次为准。
3. log4j2
使用方法
1.pom文件增加依赖:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.1</version>
</dependency>
依赖的jar包:
- log4j-api-2.8.1.jar
log4j-core-2.8.2.jar
- log4j-api: 作为日志接口层,用于统一底层日志系统
- log4j-core : 作为上述日志接口的实现,是一个实际的日志框架
2.配置文件: log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="debug">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
3.Demo
package com.jeiker.demo.controller;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* @Author : xiao
* @Date : 17/3/15 下午7:29
*/
public class Log4j2Test {
private static final Logger logger= LogManager.getLogger(Log4j2Test.class);
public static void main(String[] args){
if(logger.isTraceEnabled()){
logger.debug("log4j2 trace message");
}
if(logger.isDebugEnabled()){
logger.debug("log4j2 debug message");
}
if(logger.isInfoEnabled()){
logger.debug("log4j2 info message");
}
}
}
简单分析
(可跳过)
1. 获取底层使用的LoggerContextFactory
同样LogManager的类加载会去寻找log4j-api定义的LoggerContextFactory接口的底层实现,获取方式有三种:
第一种: 尝试从jar中寻找log4j2.component.properties文件,如果配置了log4j2.loggerContextFactory则使用该LoggerContextFactory
第二种:如果没找到,尝试从jar包中寻找META-INF/log4j-provider.properties文件,如log4j-core-2.2中就有该文件
如果找到多个,取优先级最高的(该文件中指定了LoggerContextFactory,同时指定了优先级FactoryPriority),如log4j-core-2.2中log4j-provider.properties的文件内容如下:
LoggerContextFactory = org.apache.logging.log4j.core.impl.Log4jContextFactory Log4jAPIVersion = 2.1.0 FactoryPriority= 10
第三种情况:上述方式还没找到,就使用默认的SimpleLoggerContextFactory
2.使用LoggerContextFactory获取LoggerContext
3.根据LoggerContext获取Logger
以log4j-core为例:
- 会首先判断LoggerContext是否被初始化过了,没有则进行初始化
- 获取ConfigurationFactory,从配置中获取和插件中获取(log4j-core核心包中有三个YamlConfigurationFactory、JsonConfigurationFactory、XmlConfigurationFactory)
- 以上文的案例中,会使用XmlConfigurationFactory来加载log4j2.xml配置文件
- LoggerContext初始化后,就可以获取或者创建Logger了
Log4J2 主要对象总结
LogManager
它的类加载会去寻找LoggerContextFactory接口的底层实现,会从jar包中的配置文件中寻找,如上面所述
LoggerContextFactory
用于创建LoggerContext,不同的日志实现系统会有不同的实现,如log4j-core中的实现为Log4jContextFactory
PropertyConfigurator
用于解析log4j.properties文件
LoggerContext
它包含了配置信息,并能创建log4j-api定义的Logger接口实例,并缓存这些实例
ConfigurationFactory
上述LoggerContext解析配置文件,需要用到ConfigurationFactory,目前有三个YamlConfigurationFactory、JsonConfigurationFactory、XmlConfigurationFactory,分别解析yuml json xml形式的配置文件
4. logback
使用方法
1.pom文件增加依赖:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.1</version>
</dependency>
依赖的jar包:
- slf4j-api-1.7.22.jar
- logback-classic-1.2.1.jar
- logback-core-1.2.1.jar
2.配置文件:logback.xml
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
3.Demo:
package com.jeiker.demo.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @Author : xiao
* @Date : 17/3/15 下午7:29
*/
public class LogbackTest {
private static final Logger logger = LoggerFactory.getLogger(LogbackTest.class);
public static void main(String[] args){
if(logger.isDebugEnabled()){
logger.debug("slf4j-logback debug message");
}
if(logger.isInfoEnabled()){
logger.debug("slf4j-logback info message");
}
if(logger.isTraceEnabled()){
logger.debug("slf4j-logback trace message");
}
}
}
简单分析
(可跳过)
官方使用方式,slf4j的原生实现,其实就和slf4j集成了起来。
上述的Logger、LoggerFactory都是slf4j自己的接口与类。
没有配置文件的情况下,使用的是默认配置。
1.slf4j与底层的日志系统进行绑定
在jar包中寻找org/slf4j/impl/StaticLoggerBinder.class 这个类,如在logback-classic中就含有这个类。
如果找到多个StaticLoggerBinder,则表明目前底层有多个实际的日志框架,slf4j会随机选择一个。
2.使用上述找到的StaticLoggerBinder创建一个实例,并返回一个ILoggerFactory实例
return StaticLoggerBinder.getSingleton().getLoggerFactory();
以logback-classic中的StaticLoggerBinder为例,在StaticLoggerBinder.getSingleton()过程中,会去加载解析配置文件.
源码如下:
public URL findURLOfDefaultConfigurationFile(boolean updateStatus) {
ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this);
//寻找logback.configurationFile的系统属性
URL url = findConfigFileURLFromSystemProperties(myClassLoader, updateStatus);
if (url != null) {
return url;
}
//寻找logback.groovy
url = getResource(GROOVY_AUTOCONFIG_FILE, myClassLoader, updateStatus);
if (url != null) {
return url;
}
//寻找logback-test.xml
url = getResource(TEST_AUTOCONFIG_FILE, myClassLoader, updateStatus);
if (url != null) {
return url;
}
//寻找logback.xml
return getResource(AUTOCONFIG_FILE, myClassLoader, updateStatus);
}
目前路径都是定死的,只有logback.configurationFile的系统属性是可以更改的,所以如果我们想更改配置文件的位置(不想放在类路径下),则需要设置这个系统属性:
System.setProperty("logback.configurationFile", "/path/to/config.xml");
解析完配置文件后,返回的ILoggerFactory实例的类型是LoggerContext(它包含了配置信息)
3.根据返回的ILoggerFactory实例,来获取Logger
就是根据上述的LoggerContext来创建一个Logger,每个logger与LoggerContext建立了关系,并放到LoggerContext的缓存中,就是LoggerContext的如下属性:
private Map<String, Logger> loggerCache;
其实上述过程就是slf4j与其他日志系统的绑定过程。不同的日志系统与slf4j集成,都会有一个StaticLoggerBinder类,并会拥有一个ILoggerFactory的实现。