log4j看上去像是一种简单的,易配置的日志打印技术。但是实际使用的时候发现,还有各种很相似的日志技术。很多地方的配置一乱就不知道怎么对应了。所以应该把log4j的一切做个简单的分类记录。
(一)java.util.logging.Logger
这个在java的util包里面,不需要任何Maven依赖,是最最基本的日志打印。功能很不完善,基本没有人在大型软件中使用这个。
示例代码:(日志重要级从上往下递增)
1 public class TestLog { 2 public static void main(String[] args) { 3 Logger logger = Logger.getLogger(TestLog.class.toString()); 4 logger.log(Level.ALL, "ALL"); 5 logger.log(Level.FINEST, "FINEST"); 6 logger.log(Level.FINER, "FINER"); 7 logger.log(Level.FINE, "FINE"); 8 logger.log(Level.CONFIG, "CONFIG"); 9 logger.log(Level.INFO, "INFO"); 10 logger.log(Level.WARNING, "WARNING"); 11 logger.log(Level.SEVERE, "SEVERE"); 12 logger.log(Level.OFF, "OFF"); 13 } 14 }
控制台输出:
一月 17, 2017 2:36:23 下午 com.jd.Test.TestLog main 信息: INFO 一月 17, 2017 2:36:23 下午 com.jd.Test.TestLog main 警告: WARNING 一月 17, 2017 2:36:23 下午 com.jd.Test.TestLog main 严重: SEVERE 一月 17, 2017 2:36:23 下午 com.jd.Test.TestLog main禁用: OFF
那么为什么从info开始输出呢?原因是在很多实际应用中会只在控制台打印某个级别以上的日志信息。否则在调试的时候有可能会面对海量的杂乱日志而不知如何下手。所以这里也用了这样的设计。
想要修改默认的日志输出级别的话,去jre安装目录的lib下面:
1 # Limit the message that are printed on the console to INFO and above. 2 java.util.logging.ConsoleHandler.level = INFO
那么,如何制定日志的输出目录呢?这个可以用java代码指定:
1 public class TestLog { 2 public static void main(String[] args) throws SecurityException, IOException { 3 Logger logger = Logger.getLogger(TestLog.class.toString()); 4 5 FileHandler fileHandler = new FileHandler("/Users/Davie/File/foo.log"); 6 fileHandler.setLevel(Level.ALL); 7 logger.addHandler(fileHandler); 8 9 logger.log(Level.ALL, "ALL"); 10 logger.log(Level.FINEST, "FINEST"); 11 logger.log(Level.FINER, "FINER"); 12 logger.log(Level.FINE, "FINE"); 13 logger.log(Level.CONFIG, "CONFIG"); 14 logger.log(Level.INFO, "INFO"); 15 logger.log(Level.WARNING, "WARNING"); 16 logger.log(Level.SEVERE, "SEVERE"); 17 logger.log(Level.OFF, "OFF"); 18 } 19 }
但是结果是一个xml,比较难debug。
(二)common-logging
首先,它是apache提供的一个通用的日志接口,它的存在的意义是把普遍意义上的日志和真正的日志实现解耦合。那么它本身需要什么配置呢?其实什么都不需要。因为在运行时,它会自动去找实现。这里的实现可能是上文的java.util.logging.Logger这个JDK自带的最基本的日志实现,也可能是使用广泛的Log4j。
首先在Maven加入依赖:
1 <dependency> 2 <groupId>commons-logging</groupId> 3 <artifactId>commons-logging</artifactId> 4 <version>1.1.1</version> 5 </dependency>
然后在java类中写入:
1 class Foo { 2 3 private Log log = LogFactory.getLog(TestLog.class); 4 5 public void method() { 6 // log.debug("debug"); 7 log.info("info"); 8 log.warn("warn"); 9 log.error("error"); 10 // log.fatal("fatal"); 11 } 12 }
注释的这两行去掉注释是不起作用的。原因是目前只在Maven中添加了common-logging,它会使用JDK的实现,而JDK的实现是没有debug和fatal的。那么问题是到底这个common-logging会使用哪一个Log呢?它会按照如下规则寻找其实现类(转载自:http://jiangzhengjun.iteye.com/blog/520733):
1) 首先在classpath下寻找自己的配置文件commons-logging.properties,如果找到,则使用其中定义的Log实现类;
2) 如果找不到commons-logging.properties文件,则在查找是否已定义系统环境变量org.apache.commons.logging.Log,找到则使用其定义的Log实现类;
如果在Tomact中可以建立一个叫 :CATALINA_OPTS 的环境变量
给 他的 值 : - Dorg.apache.commons.logging.Log = org.apache.commons.logging.impl.SimpleLog - Dorg.apache.commons.logging.simplelog.defaultlog = warn
3) 否则,查看classpath中是否有Log4j的包,如果发现,则自动使用Log4j作为日志实现类;
4) 否则,使用JDK自身的日志实现类(JDK1.4以后才有日志实现类);
5) 否则,使用commons-logging自己提供的一个简单的日志实现类SimpleLog;
这样呢,往往就可以保证按照用户的意愿实现log。
另外,以上的Maven依赖中如果加入了log4j相关的的依赖,反倒会在运行时报错,原因是common-logging发现了log4j,所以会使用log4j进行输出,但是这个时候log4j还没有配置于是会输出:
log4j:WARN No appenders could be found for logger (com.jd.logTest.Foo).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
(三)SLF4j
这个其实也是一种接口。它自己不实现任何真正的日志打印,而是通过再引入别的包来转换。它的最特别的好处有两个,第一是可以通过占位符来打印日志,第二是配置具体实现简单且解耦合。
对于第一点,在日志系统的使用中,往往不只是要打印出:“**异常”,还要打印出具体的异常,和异常产生时的参数环境。最简单的例子如下:
1 public void say(String word) { 2 if (word == null) { 3 log.error("输入参数异常,word:" + word); 4 return ; 5 } 6 System.out.println(word); 7 }
这样的问题是,当要打印的参数多时,要产生多个String对象。其次是代码很不直观。
对于第二点,如果没有使用这个接口,而是直接耦合了log4j。那么如果你在代码中引入了一个模块,而这个模块使用了别的日志系统,那么你就必须在你的代码里面引入那一个日志系统并且配置好,维护好。这很明显引入了不必要的工作。
那么你要怎么才能使用一个SLF4j呢?官网上有一张图:
这张图体现了SLF4j的核心思想:你只需要把正确的包引入你的工程,日志系统就能正常的工作。
那么具体如何使用SLF4j呢?首先,你需要一个最基本的SLF4j的API包:
1 <dependency> 2 <groupId>org.slf4j</groupId> 3 <artifactId>slf4j-api</artifactId> 4 <version>${slf4j.version}</version> 5 </dependency>
这个包提供了SLF4j的基本API。接着,你需要一个“转换的包”。说起转换,当然就能联想到适配器设计模式。这个地方可以理解为一个适配器。这个地方的理解是指宏观的理解:你引入了如果所示的对应的包,就把SLF4j对应的实现包引入,SLF4j就会自动去找这个包的实现。
(四)log4j
这个可以说是最享有大名的一个日志系统。确把很多初学者弄得很迷茫,因为一百度这个日志系统的实现,到处都是使用了各种上文中解耦合的接口。这是工程中常用的做法,但是刚开始确实显得比较杂乱。
--------------未完待续-----------------