Java 日志组件(一)
作为一名Java程序员,日常开发中很定会接触日志系统,但是众多的框架,包括Log4j、Log4j2、Logback、Slf4j、commons-logging等等,引用的maven依赖众多,到底可以去掉哪些,一行Loggerfactory.getLogger(LogbackTest.class)后做了
哪些工作,怎么去找的配置文件,这还是有很多细节可以研究的。
目前日志框架有jdk自带的logging、log4j、logback,目前用于实现日志统一的框架Apache的commons-logging、slf4j。为了理清它们的关系与繁杂的各种集成jar包如下:
-
-
- log4j、log4j-api、log4j-core
- log4j-1.2-api、log4j-jcl、log4j-slf4j-impl、log4j-jul
- logback-core、logback-classic、logback-access
- commons-logging
- slf4j-api、slf4j-log4j12、slf4j-simple、jcl-over-slf4j、slf4j-jdk14、log4j-over-slf4j、slf4j-jcl
-
1、jdk自带的logging
1.1、使用案例:
1 private static final Logger logger=Logger.getLogger(JdkLoggingTest.class.getName()); 2 3 public static void main(String[] args){ 4 logger.info("jdk logging info: a msg"); 5 }
其中的Logger是:java.util.logging.Logger
1.2、简单的过程分析:
- 创建一个LogManager
默认是java.util.logging.LogManager,但是也可以自定义,修改系统属性“java.util.logging.manager”即可,源码如下:
1 try { 2 cname = System.getProperty("java.util.logging.manager"); 3 if (cname != null) { 4 try { 5 Class clz = ClassLoader.getSystemClassLoader().loadClass(cname); 6 manager = (LogManager) clz.newInstance(); 7 } catch (ClassNotFoundException ex) { 8 Class clz = Thread.currentThread().getContextClassLoader().loadClass(cname); 9 manager = (LogManager) clz.newInstance(); 10 } 11 } 12 } catch (Exception ex) { 13 System.err.println("Could not load Logmanager \"" + cname + "\""); 14 ex.printStackTrace(); 15 } 16 if (manager == null) { 17 manager = new LogManager(); 18 }
- 加载配置文件
默认是jre目录下的lib/logging.properties文件,也可以自定义修改系统属性“java.util.logging.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); }
- 创建logger,并缓存起来,放置到一个Hashtable中,并把LogManager设置进新创建的logger中。
以tomcat为例,它就自定义了上述配置:
在tomcat的启动文件中catalina.bat中,有如下设置:
- 修改属性"java.util.logging.manager",自定义LogManager,使用自己的ClassLoaderLogManager
1 if not "%LOGGING_MANAGER%" == "" goto noJuliManager 2 set LOGGING_MANAGER=-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager 3 :noJuliManager 4 set JAVA_OPTS=%JAVA_OPTS% %LOGGING_MANAGER%
- 修改属性"java.util.logging.config.file",自定义配置文件,使用自己的%CATALINA_BASE%\conf\logging.properties文件
1 if not "%LOGGING_CONFIG%" == "" goto noJuliConfig 2 set LOGGING_CONFIG=-Dnop 3 if not exist "%CATALINA_BASE%\conf\logging.properties" goto noJuliConfig 4 set LOGGING_CONFIG=-Djava.util.logging.config.file="%CATALINA_BASE%\conf\logging.properties" 5 :noJuliConfig 6 set JAVA_OPTS=%JAVA_OPTS% %LOGGING_CONFIG%
所以如果想研究tomcat的日志,可以从上面入手。
2、log4j
2.1、使用案例
log4j的maven依赖
1 <dependency> 2 <groupId>log4j</groupId> 3 <artifactId>log4j</artifactId> 4 <version>1.2.17</version> 5 </dependency>
编写log4j.properties配置文件,放到类路径下
1 log4j.rootLogger = debug, console 2 log4j.appender.console = org.apache.log4j.ConsoleAppender 3 log4j.appender.console.layout = org.apache.log4j.PatternLayout 4 log4j.appender.console.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} %m%n
示例代码:
1 public class Log4jTest { 2 private static final Logger logger=Logger.getLogger(Log4jTest.class); 3 public static void main(String[] args){ 4 if(logger.isTraceEnabled()){ 5 logger.debug("log4j trace message"); 6 } 7 if(logger.isDebugEnabled()){ 8 logger.debug("log4j debug message"); 9 } 10 if(logger.isInfoEnabled()){ 11 logger.debug("log4j info message"); 12 } 13 } 14 }
补充:上述方式默认到类路径下加载log4j.properties配置文件,如果配置文件不在类路径下,可以选择如下加载方式来加载配置文件:
1 //使用classLoader来加载资源 2 PropertyConfigurator.configure(Log4jTest.class.getClassLoader().getResource("properties/log4j.properties")); 3 4 //使用log4j自带的Loader来加载资源 5 PropertyConfigurator.configure(Loader.getResource("properties/log4j.properties"));
2.1、获取Logger的原理
简单说明Logger获取的过程:
- 没有指定配置文件路径
1、LogManager初始化:
1 static public Logger getLogger(Class clazz) { 2 return LogManager.getLogger(clazz.getName()); 3 }
2、初始化一个Logger仓库Hierarchy
1 public class Hierarchy implements LoggerRepository, RendererSupport, ThrowableRendererSupport { 2 private LoggerFactory defaultFactory; 3 Hashtable ht; 4 Logger root; 5 //其他略 6 }
LoggerFactory defaultFactory就是创建的Logger工厂
Hashtable ht用来存放上述工厂创建的Logger
Logger root作为根Logger
LoggerManager在初始化的时候使用如下方式初始化Hierarchy
1 static { 2 Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG)); 3 //略 4 }
new RootLogger作为root logger,默认是debug级别,最后把Hierarchy绑定到LogManager上,可以在任何地方来获取这个logger仓库Hierarchy
3、在LogManager的类初始化的过程中默认寻找类路径下的配置文件,优先加载xml配置
1 Loader.getResource("log4j.xml"); 2 Loader.getResource("log4j.properties")
4、解析上述配置文件
5、获取Logger
- 手动加载不在类路径下的配置文件
PropertyConfigurator.configure 执行时会去进行上述的配置文件解析,源码如下:
1 public static void configure(java.net.URL configURL) { 2 new PropertyConfigurator().doConfigure(configURL, 3 LogManager.getLoggerRepository()); 4 }
-
-
仍然先会引发LogManager的类加载,创建出logger仓库Hierarchy,同时尝试加载类路径下的配置文件,此时没有则不进行解析,此时logger仓库Hierarchy中的RootLogger默认采用debug级别,没有appender而已。
-
然后解析配置文件,对上述logger仓库Hierarchy的RootLogger进行级别的设置,添加appender
-
此时再去调用Logger.getLogger,不会导致LogManager的类初始化(因为已经加载过了)
-
- 配置文件在类路径下,而我们又手动使用PropertyConfigurator去加载
也就会造成2次加载解析配置文件,仅仅会造成覆盖而已(对于RootLogger进行从新设置级别,删除原有的appender,重新加载新的appender),所以多次加载解析配置文件以最后一次为准。
2.3、主要对象的总结
-
-
-
LogManager: 它的类加载会创建logger仓库Hierarchy,并尝试寻找类路径下的配置文件,如果有则解析
-
Hierarchy : 包含三个重要属性:
- LoggerFactory logger的创建工厂
- Hashtable 用于存放上述工厂创建的logger
- Logger root logger,用于承载解析文件的结果,设置级别,同时存放appender
-
PropertyConfigurator: 用于解析log4j.properties文件
-
Logger : 我们用来输出日志的对象
-
-