Java日志

Log4j
Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件、甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;用户也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,用户能够更加细致地控制日志的生成过程。这些可以通过一个配置文件来灵活地进行配置,而不需要修改程序代码。
 配置文件log4j.properties or log4j.xml,Logger的由低到高级别如下: ALL<DEBUG<INFO<WARN<ERROR<FATAL<OFF


java -Dlog4j.configuration=. \properties\log.properties YourAppName为什么一定要使用系统属性,而不在配置文件中指定呢?很显然,如果把它写入配置文件,那么,Log4J读到它时已经迟了。


LOGBack
Logback是由log4j创始人设计的又一个开源日记组件。logback当前分成三个模块:logback-core,logback- classic和logback-access。logback-core是其它两个模块的基础模块。logback-classic是log4j的一个改良版本。此外logback-classic完整实现SLF4J API使你可以很方便地更换成其它日记系统如log4j或JDK14 Logging。logback-access访问模块与Servlet容器集成提供通过Http来访问日记的功能。
配置文件logback.xml
 
java.util.Logging
Java Logging API是sun公司于2002年5月正式发布的。它是自J2SE 1.4版本开始提供的一个新的应用程序接口。它能够很方便地控制和输出日志信息到文件,控制台或其它用户定义的地方,如数据库,电子邮件等。所以它是为最终用户,系统管理员,软件服务工程师和开发人员提供的一种捕捉安全漏洞,检查配置正确性,跟踪调查系统运行瓶颈和调查系统运行错误的工具。 Java Logging API提供了七个级别用来控制输出。分别是:
SEVERE (最高级别)
WARNING
INFO
CONFIG
FINE
FINER
FINEST (最低级别)
配置文件:logging.properties


Log4j vs. java.util.Logging
从JDK 1.4.0开始,引入了java.util.logging包。虽然Log4J小组曾竭力游说JCP(Java Community Process)采用Log4J作为JDK 1.4的“标准”日志API,虽然最终因Sun的日志API规范的负责人Graham Hamilton的一句“Merlin的开发已经到了最后阶段,这时不允许再对主要API做出改变”而没有被采纳,但Log4J还是对新的日志API产生了重要影响。那么,我们到底应该采用Log4J还是java.util.logging包呢?下面仅对两者做一简单的比较。

  1. Log4J更加成熟,从1999年10月开始至今已经有3年的时间,并且已经在许多项目中有着成熟的应用。


  而JDK中的logging包是在1.4之后才 引入的,并且不能运行于JDK 1.3之前的版本。Log4J则可以良好地运行于JDK 1.1之后的所有版本。

  2. Log4J已经被移植到多种环境下,包括log4c(C)、log4cpp(C++)、log4perl(Perl)、log4net(.net)等。在这些环境下,可以感受到几乎一致的配置和使用方式。这是JDK中的logging API所不能比拟的。

  3. Log4J还具有更加强力的格式化系统,可以使记录输出时实现简单的模式。但是,它不会增加类而导致格式化工具的扩展。众多的附加程序和处理器使得Log4J数据包成为一个绝佳的选择,所有你所需要的都可能加以实现。

  4. Log4J在性能上做了最大的优化。

  Logging API对于简单的使用是足够的,但它缺少了许多Log4J所具有的功能。所以,如果你需要一个强力的logging机制,就应坚持使用Log4J;而如果只是需要一些简单的控制或文件记录,那么可以使用已经内建在JDK之中的logging API。

虽然Log4J和JDK logging API是一种竞争关系,但在logging API还在JCP中讨论(JSR47)时,两者之间就已经开始相互影响了。

 

Log4J vs. LOGBack
LOGBack作为一个通用可靠、快速灵活的日志框架,将作为Log4j的替代和SLF4J组成新的日志系统的完整实现。LOGBack声称具有极佳的性能,“ 某些关键操作,比如判定是否记录一条日志语句的操作,其性能得到了显著的提高。这个操作在LogBack中需要3纳秒,而在Log4J中则需要30纳秒。 LogBack创建记录器(logger)的速度也更快:13微秒,而在Log4J中需要23微秒。更重要的是,它获取已存在的记录器只需94纳秒,而 Log4J需要2234纳秒,时间减少到了1/23。跟JUL相比的性能提高也是显著的”。

另外,LOGBack的所有文档是全面免费提供的,不象Log4J那样只提供部分免费文档而需要用户去购买付费文档。


SLF4J
简单日记门面(Facade)SLF4J是为各种loging APIs提供一个简单统一的接口,从而使得最终用户能够在部署的时候配置自己希望的loging APIs实现。 Logging API实现既可以选择直接实现SLF4J接的loging APIs如: NLOG4J、SimpleLogger。也可以通过SLF4J提供的API实现来开发相应的适配器如Log4jLoggerAdapter、JDK14LoggerAdapter。


Apache Common-Logging
目前广泛使用的Java日志门面库。通过动态查找的机制,在程序运行时自动找出真正使用的日志库。但由于它使用了ClassLoader寻找和载入底层的日志库,导致了象OSGI这样的框架无法正常工作,由于其不同的插件使用自己的ClassLoader。 OSGI的这种机制保证了插件互相独立,然而确使Apache Common-Logging无法工作。

 
SLF4J vs. Apache Common-Logging
SLF4J库类似于Apache Common-Logging。但是,他在编译时静态绑定真正的Log库。使用SLF4J时,如果你需要使用某一种日志实现,那么你必须选择正确的SLF4J的jar包的集合。如此便可以在OSGI中使用了。

另外,SLF4J 支持参数化的log字符串,避免了之前为了减少字符串拼接的性能损耗而不得不写的if(logger.isDebugEnable()),现在你可以直接写:logger.debug(“current user is: {}”, user)。拼装消息被推迟到了它能够确定是不是要显示这条消息的时候,但是获取参数的代价并没有幸免。同时,日志中的参数若超过三个,则需要将参数以数组的形式传入,如:现在,Hibernate、Jetty、Spring-OSGi、Wicket和MINA等项目都已经迁移到了SLF4J,由此可见SLF4J的影响力不可忽视。


如果在单纯的logging环境中,使用SLF4J意义不大。如果想在各种logger API中切换,SELF4J是理想选择,另外在新的项目中,使用SLF4J+Logback是比较好的日志框架选型。

 

 


 如果对于commons-loging 、log4j 、slf4j 、LogBack 等都已经非常清楚了,可以忽略本文。几次解决日志冲突问题时对这几个概念的简单总结,希望对这块基础没有理解透的同学能有所帮助,当然如果对这块有更深刻理解的同学,也贡献出自己的知识和见解。

一、     概念

Commons-logging : apache最早提供的日志的门面接口。避免和具体的日志方案直接耦合。类似于JDBC 的api 接口,具体的的JDBC driver 实现由各数据库提供商实现。通过统一接口解耦,不过其内部也实现了一些简单日志方案。

Log4j : 经典的一种日志解决方案。内部把日志系统抽象封装成Logger 、appender 、pattern 等实现。我们可以通过配置文件轻松的实现日志系统的管理和多样化配置。

Slf4j : 全称为Simple Logging Facade for JAVA:java简单日志门面。是对不同日志框架提供的一个门面封装。可以在部署的时候不修改任何配置即可接入一种日志实现方案。和commons-loging 应该有一样的初衷。个人感觉设从计上更好一些,没有commons 那么多潜规则。同时有两个额外特点:

1. 能支持多个参数,并通过{} 占位符进行替换,避免老写logger.isXXXEnabled 这种无奈的判断,带来性能提升见:http://www.slf4j.org/faq.html#logging_performance

2.OSGI 机制更好兼容支持

一图胜千言,官网上的一个图:

 

从上图可以发现,选择还是很多的。

Logback : LOGBack 作为一个通用可靠、快速灵活的日志框架,将作为Log4j 的替代和SLF4J 组成新的日志系统的完整实现。官网上称具有极佳的性能,在关键路径上执行速度是log4j 的10 倍,且内存消耗更少。具体优势见:

http://logback.qos.ch/reasonsToSwitch.html

二、     常见日志方案和注意事项

1.Commons-logging+log4j : 经典的一个日志实现方案。出现在各种框架里。如spring 、webx 、ibatis 等等。直接使用log4j 即可满足我们的日志方案。但是一般为了避免直接依赖具体的日志实现,一般都是结合commons-logging 来实现。常见代码如下:

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

private static Log logger = LogFactory.getLog(CommonsLoggingTest.class);

代码上,没有依赖任何的log4j 内部的类。那么log4j 是如何被装载的?

Log 是一个接口声明。LogFactory 的内部会去装载具体的日志系统,并获得实现该Log 接口的实现类。而内部有一个Log4JLogger 实现类对Log 接口同时内部提供了对log4j logger 的代理。LogFactory 内部装载日志系统流程:

1.   首先,寻找org.apache.commons.logging.LogFactory 属性配置

2.   否则,利用JDK1.3 开始提供的service 发现机制,会扫描classpah 下的META-INF/services/org.apache.commons.logging.LogFactory 文件,若找到则装载里面的配置,使用里面的配置。

3.   否则,从Classpath 里寻找commons-logging.properties ,找到则根据里面的配置加载。

4.   否则,使用默认的配置:如果能找到Log4j 则默认使用log4j 实现,如果没有则使用JDK14Logger 实现,再没有则使用commons-logging 内部提供的SimpleLog 实现。

从上述加载流程来看,如果没有做任何配置,只要引入了log4j 并在classpath 配置了log4j.xml ,则commons-logging 就会使log4j 使用正常,而代码里不需要依赖任何log4j 的代码。

2.Commons-logging+log4j+slf4j

如果在原有commons-logging 系统里,如果要迁移到slf4j, 使用slf4j 替换commons-logging ,也是可以做到的。原理使用到了上述commons-logging 加载的第二点。需要引入Org.slf4j.jcl-over-slf4j-1.5.6.jar 。这个jar 包提供了一个桥接,让底层实现是基于slf4j 。原理是在该jar 包里存放了配置META-INF/services/org.apache.commons.logging.LogFactory =org.apache.commons.logging.impl.SLF4JLogFactory ,而commons-logging 在初始化的时候会找到这个serviceId ,并把它作为LogFactory 。

完成桥接后,那么那么简单日志门面SLF4J 内部又是如何来装载合适的log 呢?

原理是SLF4J 会在编译时会绑定import org.slf4j.impl.StaticLoggerBinder; 该类里面实现对具体日志方案的绑定接入。任何一种基于slf4j 的实现都要有一个这个类。如:

org.slf4j.slf4j-log4j12-1.5.6: 提供对 log4j 的一种适配实现。

Org.slf4j.slf4j-simple-1.5.6: 是一种 simple 实现,会将 log 直接打到控制台。

……

那么这个地方就要注意了:如果有任意两个实现slf4j 的包同时出现,那就有可能酿就悲剧,你可能会发现日志不见了、或都打到控制台了。原因是这两个jar 包里都有各自的org.slf4j.impl.StaticLoggerBinder ,编译时候绑定的是哪个是不确定的。这个地方要特别注意!!出现过几次因为这个导致日志错乱的问题。

3.Slf4j+logback

Slf4j 和log4j 作者都是同一个人。

Logback 号称在性能各方面有很多优势,也很诱人。

直接使用SLf4j 也很简单:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {
  public static void main(String[] args) {
    Logger logger = LoggerFactory.getLogger(HelloWorld.class);
    logger.info("Hello World");
  }
}

代码里也看不到任何具体日志实现方案的痕迹。

Logback 没用过,看到过一些诱人介绍。具体大家可以去研究。logback 。

注意事项

使用日志配置的时候一定要明白需求,同时避免冲突。

如使用SLF4j 的时候为了避免冲突,一定要保障只有一种实现类jar 包在里面。

当遇到日志错乱等问题时,可以从这几个方面来排查

 

 

 

 

J2SDK1.4的Java日志框架,其实总结起来主要是下面几点:

命名空间:空间外层包含内层的设置 
Handler:可以将日志信息放入内容,定向到文件,或控制台等 
Lever:规定日志的级别,低级别的日志可以被忽略 
Formatter:负责将日志进行格式化,这样出来的东西比较好看一点


引言 
    作为一名Java 程序员,最熟悉的、使用最多的调用恐怕莫过于System.out.print(“…”)。当你没有调试工具而要跟踪一个变量的值得时候;当你需要显示捕获的Exception、Error的时候;当你想知道程序在运行的时候究竟发生了什么的时候,通常的做法就是调用System.out.print把他们在终端、控制台上打印出来。这种方式对于输出信息的分类、格式化及永久保存带来诸多不便。虽然我们可以把它写入一个文件然后进行分析,但是这要需要编写额外的程序代码,其成本不可忽视!而由此给目标系统本身增加的复杂程度不可避免的使开发、调试陷入一个深深的迷潭。 
    JDK1.4的推出,使得这一切即将成为历史。让我们向System.out.print()告别吧,使用Java Logging API为自己的程序构建一个完整的日志记录系统! 

一、第一个实例 
先看一个简单的实例:SimpleLoggingTest.java 
import java.util.logging.*; 
public class SimpleLoggingTest { 
     public static void main(String args[]) { 
       //程序的其它处理 
       //使用Logger的静态方法获得一个匿名Logger 
       Logger logger1 = Logger.getAnonymousLogger(); 
      //记录消息 
      logger1.log(Level.INFO,"第一条日志记录"); 
       //程序的其它处理 
    } 

实例1 
注意:编译、执行该程序需要JDK1.4及以上版本的支持。 
运行该程序,可以在控制台看到程序运行结果: 
2003-1-14 15:09:40 SimpleLoggingTest main 
信息: 第一条日志记录 

     首先,程序引用java.util.Logging包(第1行)。接着,在适当的时候获得一个Logger(记录器)类的实例(第6行,获取一个匿名的Logger)。最后,在程序需要记录信息的地方调用Logger类的log方法进行记录(第8行,记录一个INFO级别的消息)。 

二、Java Logging API 
     Java Logging API封装在JDK1.4.0的java.util.Logging包中。它通过产生便于最终用户、系统管理员、故障维护工程师以及软件开发团队(工程师)进行分析的日志记录为软件的开发调试和维护提供便利的手段。它捕获操作系统平台和执行程序的安全故障、配置错误、执行瓶颈和(或)Bug等数据信息,以纯文本、XML或程序员自定的某种方式将其格式化成日志记录,然后传递给内存、系统输出流、控制台、文件、Sockets等多种系统资源进行缓存和输出。
 
(一)、该软件包中的关键类。 
.. Logger: 应用程序进行日志记录调用的主要实体。 Logger对象用于记录特定系统或应用程序的消息。 
.. LogRecord: 用于在日志框架和单个记录处理程序之间传递记录请求。 
.. Handler: 日志数据的最终输出处理器。它将LogRecord对象导出到各种目标,包括内存、输出流、控制台、文件和套接字。多种 Handler子类可供用于这种用途。 

.. Level: 定义一组标准的记录级别,可用于控制记录的输出。可以把程序配置为只输出某些级别的记录,而忽略其他级别的输出。 
.. Filter: 精细过滤、控制记录的内容,比记录级别所提供的控制准确得多。

记录API支持通用的过滤器机制,这种机制允许应用程序代码添加任意过滤器以便控制记录的输出。 
.. Formatter: 为LogRecord对象的格式化提供支持。 

.. LogManager: Java Logging框架中唯一的、全局的对象,用于维护与Logger记录器及日志服务的一系列共享的数据结构及状态。它负责整个日志框架的初始化、维护一组全局性的Handle对象、 维护一个树形结构的Logger的名字空间、诊测日志框架配置文件的改变从而重新读入并应用相关的参数以及负责程序停止运行时整个日志框架的清理工作。 
(二)Logger 

1、Logger的命名空间 
在SimpleLoggingTest.java实例中,我们使用了一个匿名的(没有命名的)Logger对象。在Java Logging 框架中,Logger是可以命名的。Logger的名字空间与java类的名字空间相同的结构相同:使用“.”间隔的字符串。Logger的名字空间体现了Logger的层次结构。例如:命名为“a.b”的Logger是命名为“a.b.c”的“父”(上一级)Logger记录器。Logger的命名可以是任意的字符串,一般情况下,使用包或类的名字为Logger 进行命名。 Logger的名字空间由全局单列类LogManager的实例进行创建、维护。 匿名Logger不被存储在命名空间中。 

2、创建Logger实例 
Logger对象可以通过调用工厂方法getLogger或getAnonymousLogger获取。 
   //获取一个名为“A”的Logger对象 
   Logger loggerA= Logger.getLogger(“A”); 
   // 获取一个名为“A.B”的Logger对象,其上级记录器为loggerA。 
   Logger loggerAB= Logger.getLogger(“A.B”); 
   //获取一个匿名Logger对象 
   Logger loggerTmp = Logger.getAnonymousLogger(); 

   对非匿名Logger,getLogger先在命名空间中查找同名的Logger对象,如果有,则返回该Logger对象;如果不存在,则在命名空间中创建注册一个新的Logger对象,并与其上级Logger对象相关联。匿名Logger对象属于创建它的对象的私有对象,只能由创建它的对象使用,记录一些临时性的日志信息。而命名Logger对象是全局性的,在日志框架的生存期内,除了创建它的对象外还,可由其它对象用于记录日志信息。

(三)、Handler 
    Handler对象接收传来的日志消息将其输出。Handler可以把日志消息输出到多种目标资源,如:输出到控制台进行显示、写入日志文件、传送到网络上的远程日志服务进行处理、写入系统日志等任何物理资源。 Handler对象在创建时使用LogManager对象的相关属性的默认值(如Handler的Filter、Formatter、Level等对象属性)进行初始化。Handler对象可通过调用setLevel(Level.OFF)暂停工作;通过调用setLevel设置适当的记录日志消息级别恢复工作。 Handler是一个抽象类。在J2SDK1.4中,其子类及它们之间的关系见图一。 

1、 MemoryHandler Handler的子类,在内存中的一个循环缓冲区用于缓存日志记录请求。通常MemoryHandler只简单的把传入的LogRecords存储到它的内存中。这种缓存的开销非常低廉,它去掉了格式化所产生的系统消耗。当某个触发条件满足时,MemoryHandler将其缓冲的数据push(发布)到目标Handler,由后者执行实际的输出。有三种模式触发MemoryHandler进行push操作:a、传入的LogRecords的级别高于MemoryHandler预先定义的push级别;b、有其他对象显式的调用其push方法;c、其子类重载了log方法,逐一检索每个传入的LogRecords,若符合特定的标准则进行push操作。
 
实例:
    假设我们需要跟踪一个生产环境中的一个很少出现的Bug。在大多数场合,系统化产生大量的日志记录,而我们仅只关心记录中最近的几条,那么我们只需要使用MemoryHandler对日志记录进行缓存,当且仅当某个事件
发生时将最近的几条记录从内存中dump到制定的文件中。 

//MemoryHandlerTest.java 
import java.util.logging.*; 
import java.io.*; 
public class MemoryHandlerTest { 
   FileHandler fhandler; 
   Logger logger; 
   MemoryHandler mhandler; 
   MemoryHandlerTest() { 
   try { 
      //构造名为my.log的日志记录文件 
      fhandler = new FileHandler("my.log"); 
      int numRec = 5; 
     //构造一个5个日志记录的MemoryHandler, 
     //其目标Handler为一个FileHandler 

     mhandler = new MemoryHandler (fhandler, numRec, Level.OFF) ; 
     //构造一个记录器 
     logger = Logger.getLogger("com.mycompany"); 
     //为记录器添加一个MemoryHandler 
     logger.addHandler(mhandler); 
   } catch (IOException e) { } 

   public static void main(String args[]) { 
      MemoryHandlerTest mt = new MemoryHandlerTest(); 
      int trigger = (int)(Math.random()*100); 
      for (int i=1;i<100;i++) { 
             //在MemoryHandler中缓存日志记录 
              mt.logger.log(Level.INFO,"日志记录"+i); 
              if (i==trigger) { 
                  //触发事件成立,显式调用MemoryHandler的 
                  //push方法触发目标Handler输出日志记录到 
                  //my.log文件中 
                  mt.mhandler.push(); 
                  break; 
              } 
      } 
   } 

实例2 
2、 FileHandler 文件处理器。 
StreamHandler流处理器将日志记录以流的形式输出。FileHandler、ConsoleHandler、SocketHandler为StreamHandler的子类。 ConsoleHandler将日志记录输出到控制终端。前面的实例(实例2除外)都将日记记录数据输出到控制台。 
FileHandler将日志记录输出到特定的文件,或循环的几个日志文件中。日志文件可以设置容量大小。当日志文件达到限定的容量时将被自动清空,重头开始写入新的日志记录数据。 

例:创建一个容量为1Mb的文件处理器 
int limit = 1000000; // 1 Mb 
FileHandler fh = new FileHandler("my.log", limit, 1); 
对于循环的日志文件,每个文件将被指定容量限制。当当前的日志文件的长度达到制定值后该文件被关闭,新的日志文件被创建,旧的文件将在文件名模板后追加序号。如此产生多个顺序编号的日志记录文件。 

例: 
try { 
      // 创建一个拥有3个日志文件,每个容量为1Mb的文件处理器 
      String pattern = "my%g.log"; 
      int limit = 1000000; // 1 Mb 
      int numLogFiles = 3; 
      FileHandler fh = new FileHandler(pattern, limit, numLogFiles); 
… 
} catch (IOException e) { } 

(四)、Level 
Level对象定义了一组日志消息的级别,用于控制日志消息的输出。一个级别对应一个整型值。日志消息级别按照其整数值的大小排定优先级。在Logger对象中设定一个级别,则大于等于该级别的日志消息将会被传递到某个Handler对象进行输出处理。 
J2SDK1.4的Java Logging框架中定义了以下消息级别: 
级别名称 
int值(Windows 2000环境) 
OFF      2147483647 
SEVERE   1000 
WARNING 900 
INFO     800 
CONFIG   700 
FINE     500 
FINER    400 
FINEST   300 
ALL      -2147483648 
表一 
Level.OFF具有最高的级别。将Logger的Level级别设置成Level.OFF 让我们看看消息级别是怎样工作的:
LoggingLevelTest.java 
import java.util.logging.*; 
public class LoggingLevelTest { 
    public static void main(String args[]) { 
        //使用Logger的静态方法获得一个匿名Logger 
        Logger logger1 = Logger.getAnonymousLogger(); 
        //设置Logger对象记录的最低日志消息级别 
        logger1.setLevel(Level.FINER); 
        //记录消息 
        logger1.severe("SEVERE级消息"); 
        logger1.warning("WARNING级消息"); 
        logger1.config("CONFIG级消息"); 
        logger1.info("INFO级消息"); 
        logger1.fine("FINE级消息"); 

        logger1.finer("FINER级消息"); 
        logger1.finest("FINEST级消息"); 
   } 

实例3 
运行结果: 
2003-1-15 7:02:03 LoggingLevelTest main 
服务器: SEVERE级消息 
2003-1-15 7:02:04 LoggingLevelTest main 
警告: WARNING级消息 
2003-1-15 7:02:04 LoggingLevelTest main 
配置: CONFIG级消息 
2003-1-15 7:02:04 LoggingLevelTest main 
信息: INFO级消息 
可以看出,优先级低于INFO的日志消息不被记录。 
Level的构造函数为protected便于程序员开发自己的消息级别类。 

import java.util.logging.*; 
//自定义消息级别 
class myLevel extends Level{ 
//定义自己的消息级别SYSE 
   public static final Level SYSE = new myLevel("SYSE", Level.SEVERE.intValue()+10); 
      public myLevel(String ln,int v) { 
         super(ln,v); 
      } 

public class MyLevelTest { 
public static void main(String args[]) { 
     Logger logger1 = Logger.getAnonymousLogger(); 
     //设置消息级别 
    logger1.setLevel(myLevel.SYSE); 
    //记录消息 
    logger1.log(myLevel.SYSE,"SYSE消息"); 
    logger1.severe("SVERE消息"); 


实例4 
运行结果: 

2003-1-15 15:40:04 MyLevelTest main 
SYSE: SYSE消息 
只有SYSE消息被记录,SVERE消息不被记录,因为自定义级别SYSE高于SEVERE. 

(五)Formatter 
     Formatter负责对LogRecords进行格式化。每个记录吕砥鱄andler同一个Formatter对象相关联。Formatter对象接收从Handler传来的LogRecord,将其格式化成字符串后返回给Handler进行输出。 

Formatter是一个抽象类。在J2SDK1.4中,其子类及它们之间的关系见图二。 
自定义扩展Formatter类。
实例:MyFormatterTest.java 

import java.util.Date; 
import java.util.logging.*; 
//创建每条日志记录以行的日志格式: 
//时间<空格>消息级别<空格>消息ID<空格>日志信息内容<换行> 
class MyFormatter extends Formatter { 
      public String format(LogRecord rec) { 
         StringBuffer buf = new StringBuffer(1000); 
         buf.append(new Date().toLocaleString()); //时间 
         buf.append(' '); 
         buf.append(rec.getLevel()); //消息级别 
         buf.append(' '); 
         buf.append(rec.getMillis()); //作为消息ID 
         buf.append(' '); 
         buf.append(formatMessage(rec));//格式化日志记录数据 
         buf.append('\n'); //换行 
         return buf.toString(); 
     } 


public class MyFormatterTest { 
   public static void main(String args[]){ 
     //创建记录器 
     Logger log1 = Logger.getLogger("MyLogger"); 
     //创建记录处理器 
     Handler mh = new ConsoleHandler(); 
    //为记录处理器设置Formatter 
     mh.setFormatter(new MyFormatter()); 
    //为记录器添加记录处理器 

    log1.addHandler(mh); 
    //禁止消息处理将日志消息上传给父级处理器 
    log1.setUseParentHandlers(false); 
    //记录消息 
    log1.severe("消息1"); 
    log1.warning("消息2"); 
    log1.info("消息3"); 
    log1.config("消息4"); 
   } 

实例5 
程序运行结果: 
2003-1-15 16:59:38 SEVERE 1042621178968 消息1 
2003-1-15 16:59:40 WARNING 1042621178985 消息2 
2003-1-15 16:59:41 INFO 1042621179105 消息3 

三、配置文件 
J2SDK1.4的Java Logging框架的配置文件(Windows): %J2SDK1.4_HOME%/jre/lib/logging.properties 
从配置文件可以看到: 
(一) 自定义日志配置文件: 
java -Djava.util.logging.config.file=myfile 
(二)全局Handler在Java VM启动时被加载。 
(二) 全局Handler默认为java.util.logging.ConsoleHandler。 
handlers= java.util.logging.ConsoleHandler 
所以我们的任何日志记录动作都会在控制台进行显示。 
(三) 缺省的消息记录级别为:INFO 
.level= INFO 
在缺省情况下我们在控制台看不见低于INFO级别的日志消息。 
(四) 缺省的Handler消息格式为java.util.logging.SimpleFormatter 

四、日志框架在程序测试中的应用 
Logger类提供了两个的方法:Logger.entering()、 ogger.exiting() 。
这对我们调试自己的方法调用提供了便利的方式。 
例子: 
记录方法调用的输入参数和输出参数 
方法myMethod将一个int 追加在一个对象之后。 
运行该程序应将logging.properties的 
java.util.logging.ConsoleHandler.level = INFO 
改为: 
java.util.logging.ConsoleHandler.level = ALL 

import java.util.logging.*; 
public class MyClass { 
public String myMethod(int p1, Object p2) { 
    Logger logger = Logger.getLogger("com.mycompany.MyClass"); 
    if (logger.isLoggable(Level.FINER)) { 
         logger.entering(this.getClass().getName(), "myMethod", new Object[]{new Integer(p1), p2}); 
    } 
    String tmp = p2.toString() + p1; 
    if (logger.isLoggable(Level.FINER)) { 
             logger.exiting(this.getClass().getName(), "myMethod", tmp); 
    } 
    return tmp; 

     public static void main(String args[]) { 
        MyClass mc = new MyClass(); 
        String rslt = mc.myMethod(123,"Hello"); 
     } 

后记 
J2SDK1.4引入的日志记录框架为构建简易的日志记录系统提供了便利的解决方案。虽
然还有期它的一些专用日志包如Log4j,但从简单的打印输出到严密的、可扩展的日志记录
框架,J2SDK1.4的日志系统已经足以满足一般的系统开发的要求。

 

 

 

 

 

概述
本文根据作者的开发经验叙述了对Java程序调试和日志的认识和理解。介绍了System.out.println, System.out.printf,手工制作的小工具类SysUtils.log,log4j和logback。

引言
在Java开发中,除了IDE提供的调试工具之外,监控代码中变量变化,跟踪代码运行有很多种方法:

1、招之即来的System.out.println
System.out.println无疑是最方便的,受众也是最广,知名度最高。Java学习者往往从第一个HelloWorld的例子开始就接触它,学习Java的人应该没有人不知道它。

示例代码:

System.out.println(sql);

System.out.println("My name is " + name + ", I am " + age + " years old.");

优点是显而易见的,缺点也不少,如:

只能输出到控制台;

大量的代码混在业务逻辑中,在生产环境中需要处理这些影响性能的代码。

字符串拼接容易带来性能损失(当然可以使用StringBuffer,但又稍显繁琐)

2、Java5对System.out.println的改进:System.out.printf
转向Java的C程序员无比怀念C语言中的printf,Java5也对此提供了支持,Java也开始为开发者着想了,看代码:

System.out.println(sql);

System.out.printf("My name is %S, I am %d years old.", name, age);


C程序员对此最熟悉不过了,无论是可读性和效率都比上面的好很多,唯一的缺点就是需要Java 5 的支持。

3、土制的utils小工具
System.out.println,带来的最大问题是从开发环境向生产环境转换时带的性能问题(当然其它的问题也不少),部署时应该去除或注释这些代码,。

解决这个问题最好的办法是土制一个小工具类,look:

public class SysUtils {

public static final void log(Object o) {

System.out.println("==" + String.valueOf(o));

}

}

以后的调试代码全部调用上面的静态方法,代码如下:

SysUtils.log(sql);

SysUtils.log("My name is " + name + ", I am " + age + " years old.");


部署的时候,只要把SysUtils.log里的System.out.println 注释掉就万事大吉了。

4、进阶过程,日志小知识
然而一切似乎还是没有改变,功能太弱,和代码耦合性太强...于是日志(log)出现了。日志,源于log,有航海日志的意思。指记录海员记录每天的行程,生活及发生的事件。在软件开发领域,用来监控代码中变量变化,跟踪代码运行的轨迹,在开发环境中担当调试器作用,向控制台或文件输出信息,运行环境中记录程序的警告和错误信息。

Java开源牛人们为了改变现状,不断开发出各种各样的日志工具,于是Java程序员受苦了,不得不在各种日志间转换奔波。

然而天下大事,分久必合,合久必分。于是乎commons-logging出现了。它提供了日志统一的接口和一个最简单的实现。Java程序员幸福了,因为只需要针对接口编程。而不管到底由谁来实现。


最著名的几个实现有:

Simplelog:最简单的实现。

Jul:java.util.logging,JDK中自带的日志实现

log4j:Apache Software Foundation开发的非常强大的日志实现


log主要有几个个概念:

输出级别:是调试信息,信息,还是警告,错误,致命错误等

输出目的地:输出到控制台,文件,可写设备,还是数据库

输出格式和内容:输出哪些东西,如何排版等。


5、log4J
Log4J的意思是Log for Java,4是for的简写,当然也读作 “for”


Log4J的输出级别:

Log4j建议只使用四个级别,优先级从高到低分别是ERROR、WARN、INFO、DEBUG。

Log4J的输出目的地:

ConsoleAppender(控制台)       

FileAppender(文件) 

DailyRollingFileAppender(每天产生一个日志文件)

RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)

WriterAppender(将日志信息以流格式发送到任意指定的地方)

甚至能输出到数据库

Log4J的输出格式:

HTMLLayout(以HTML表格形式布局)

PatternLayout(可以灵活地指定布局模式)

SimpleLayout(包含日志信息的级别和信息字符串)

TTCCLayout(包含日志产生的时间、线程、类别等等信息)

Log4J的输出内容:

Log级别

类名

线程名

时间

位置等等。

强大到足以在开发环境或生产环境做一切你能想到的记录。


Log4J在Java Web项目中的使用方法:

1.       commons-logging.jar, log4.jar扔到lib目录

2.       log4j.properties文件扔到classes根目录

3.       OK了。


示例代码:

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;


public class TestLog {

Log log = LogFactory.getLog(TestLog.class);

public void print() {

if (log.isDebugEnabled()) {

log.debug (sql);

log.debug ("My name is " + name + ", I am " + age + " years old.");

}

}

}


6、slf4j和logback,没有最好,只有更好
log4j经过这么久的发展,从外面看,经典,庄重,强壮,从内部看,却充满了代码的坏味道。就连它的作者也认为应该有一个更好的日志框架。于是,再次操刀,创建了slf4j来取代jcl,创建了logback来取代log4j。

目前,log4j和jul应用最为广泛,slf4j作为新兴的抽象层,整合logback,以其简洁,快速,正被越来越多的顶级项目使用。如大名鼎鼎的hibernate,Jetty。

参见:http://www.slf4j.org/


十个转移到logback的理由(http://logback.qos.ch/10reasons.ppt


slf4j支持参数化的logger.error("帐号ID:{}不存在", userId);告别了if(logger.isDebugEnable()) 时代。


另外logback的整体性能比log4j也较佳,hibernate等项目已经采用了slf4j:


"某些关键操作,比如判定是否记录一条日志语句的操作,其性能得到了显著的提高。这个操作在LOGBack中需要3纳秒,而在Log4J中则需要30纳 秒。 LOGBack创建记录器(logger)的速度也更快:13毫秒,而在Log4J中需要23毫秒。更重要的是,它获取已存在的记录器只需94纳秒,而 Log4J需要2234纳秒,时间减少到了1/23。"


Slf4j    相当于commons-logging 提供了一系列的日志接口

Logback相当于 log4j                        提供了强大的实现


作者也称Logback是可靠,通用,快速,灵活的java日志工具(官方描述)。


commons-logging 和slf4j的代码比较:

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

public class TestLogBySlf4J {

Logger logger = LoggerFactory.getLogger(TestLogBySlf4J.class);

public void print() {

logger.debug(sql);

logger.debug("My name is {}, I am {} years old.", name, age);

}

}


依稀又有了一点Java5的影子。


项目选择的思考。
如果是简单的项目,如小工具,小Demo等,采用System.out.printf , 土制工类,jul,都是不错的选择。


如果是web开发,宜采用log4j,因为系统已经有了很多的配置文件,不在乎多一个,而且功能强大。公司目前很多项目采用log4j。


如果是较新的项目,可采用slf4j,在学习成本不高的情况下,获得更好的架构,灵活性和性能。

小结
限于篇幅,本文只简要介绍了从原始的内置类到简单的封装,再到Log4j,再到强大的logback的进阶过程和主要示例代码,中间细节请查阅官方详细的文档。

参考资料
Slf4j官方网站(http://www.slf4j.org/)。

Logback官方网站(http://logback.qos.ch/)。


Jcl官方网站(http://commons.apache.org/logging/)。


Log4j官方网站(http://logging.apache.org/)。

SpringSide 官方wiki(http://wiki.springside.org.cn/)。


Java日志系统研究(http://yanboy.javaeye.com/blog/204436

 

 

 

 

 

1.   使用Jakarta Commons Logging(JCL)
1.1. 概述

Apache的开源日志组件Jakarta Commons Logging(JCL)提供的是一个日志(Log)接口(Interface),同时兼顾轻量级和不依赖于具体的日志实现工具。它提供给中间件/日志工具开发者一个简单的日志操作抽象,允许程序开发人员使用不同的具体日志实现工具。用户被假定已熟悉某种日志实现工具的更高级别的细节。JCL提供的接口,对其它一些日志工具,包括Log4J, Avalon LogKit, and JDK 1.4等,进行了简单的包装。Commons-logging的目的是为“所有的JAVA日志实现”提供一个同一的接口,它本身的日志功能比较弱(只有一个简单的SimpleLog实现类),所以一般不会单独使用它,可以结合功能强大的Log4j使用。

 

1.2. 配置commons-logging

1.2.1            将commons-logging.jar导入工程,若使用log4j还需导入log4j-1.2.8.jar

1.2.2            JCL首先在classpath下寻找自己的配置文件commons-logging.properties,如果找到就是使用其中的Log实现类

commons-logging.properties文件

# commons-logging.properties

#org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog

# simplelog.properties

# # Logging detail level,

# # Must be one of ("trace", "debug", "info", "warn", "error", or "fatal").

#org.apache.commons.logging.simplelog.defaultlog=trace

org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog

1.2.3            如果上面的步骤失败,Commons的Logging接着检查系统属性org.apache.commons.logging.Log。

1.2.4            如果找不到org.apache.commons.logging.Log系统属性,Logging接着在CLASSPATH中寻找log4j的类。如果找到了,Logging就假定应用要使用的是log4j。不过这时log4j本身的属性仍要通过log4j.properties文件正确配置。

1.2.5            如果上述查找均不能找到适当的Logging API,但应用程序正运行在JRE 1.4或更高版本上,则默认使用JRE 1.4的日志记录功能。

1.2.6            最后,如果上述操作都失败,则应用将使用内建的SimpleLog。SimpleLog把所有日志信息直接输出到System.err。

1.3. 使用Log

1.3.1            导入所需的commons-logging类

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;
 

                 

1.3.2            利用LogFactory创建Log实例

private static final Log log = LogFactory.getLog(ApacheLoggingDemo.class);
 

1.3.3            使用org.apache.commons.logging.Log类的成员方法输出日志信息

log.info("commons-logging info message");

log.debug("debug message");

log.error("error message");

log.warn("warnning message");

log.trace("trace message");
 

 

2.   使用JDK1.4 Logger
2.1. 概述

sun在JDK1.4提供了一个专门处理日志的记录包:java.util.logging,它可以对程序中的日志记录进行相当复杂的控制。例如:通过它可以指定日志级别和日志的位置(控制台、文件、Email等),我们可以自己手动创建子记录器,通过它可以用程序控制的方式来指定记录的内容,也可以使用配置文件来指定,而不需要改动程序。

2.2. 使用Logger

2.2.1            在工程中创建配置文件logging.properties,内容如下:

# "handlers" specifies a comma separated list of log Handler

handlers = java.util.logging.FileHandler,java.util.logging.ConsoleHandler

             

# Default global logging level.

.Level = INFO

 

# default file output is in user's home directory.

java.util.logging.FileHandler.pattern = runtime.log

java.util.logging.FileHandler.limit = 50000

java.util.logging.FileHandler.count = 2

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.FileHandler.level = INFO

java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

 

说明:通过以上的配置,在处理日志时,就会将日志信息写入runtime.log文件中,并且在控制台也显示,若在工程中没有配置logging.properties文件,系统就会自动加载jre/lib/logging.properties配置文件内容,默认是ConsoleHandler意味着日志信息在控制台显示。

 

2.2.2            创建日志管理器LogManager实例并加载配置文件(logging.properties)

LogManager logMgr = LogManager.getLogManager();

FileInputStream fin = new FileInputStream(new File(

                                   "src/logging.properties"));

logMgr.readConfiguration(fin);
 

2.2.3            创建记录器Logger,并将其添加到当前日志管理器                              

log = Logger.getLogger(this.getClass().getName());

logMgr.addLogger(log);
 

                                 

2.2.4            使用记录器记录日志

                     

log.fine("the fine message");

log.warning("the warning message");

log.info("the info message");

log.severe("The severe message");
 

 

3.   使用Log4j
3.1. 概述

Log4j是Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件、甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。

3.2. Log4j配置

3.2.1            加入log4j-1.2.8.jar到你的工程当中

3.2.2            在工程的classpath下创建log4j.properties配置文件,内容如下:

### direct log messages to stdout ###

log4j.rootLogger=info,stdout

log4j.rootLogger=debug,stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender

log4j.appender.stdout.Target=System.out

log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

log4j.appender.stdout.layout.ConversionPattern=%d{yyy-MM-dd HH:mm:ss } [%t] %5p %c{1}:%L - %m%n

 

#log4j.logger.org.hibernate=fatal

#log4j.logger.net.sf.hibernate=fatal

 

#log4j.logger.net.sf.hibernate.SQL=fatal

 

#log4j.logger.net.sf.hibernate.type=fatal

 

#log4j.logger.net.sf.hibernate.tool.hbm2ddl=fatal

 

# Set root logger level to DEBUG and its only appender to A1.

 

#log4j.rootLogger=debug, stdout, R

#log4j.rootLogger=debug,R

#log4j.appender.stdout=org.apache.log4j.ConsoleAppender

#log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

 

# Pattern to output the caller's file name and line number.

#log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n

# Print the date in ISO 8601 format

#log4j.appender.stdout.layout.ConversionPattern=%d [%t] %-5p %c - %m%n

 

#log4j.appender.R=org.apache.log4j.RollingFileAppender

#log4j.appender.R.File=example.log

 

#log4j.appender.R.MaxFileSize=100KB

# Keep one backup file

#log4j.appender.R.MaxBackupIndex=1

 

#log4j.appender.R.layout=org.apache.log4j.PatternLayout

#log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n

 

# Print only messages of level WARN or above in the package com.foo.

#log4j.logger.com.foo=WARN

3.2.3            在输出日志信息的类中使用Log4j

              

 

得到Log实例还有另外一种方式(利用LogFactory):

private static final Log logx = LogFactory.getLog(Log4jTest.class);
 

                   使用这种方式时要用到struts的commons-logging.jar

在实际应用中commons-logging+log4j无疑是最好的选择

3.3. Log4j配置文件详解

3.3.1        根类别

 

配置根Logger,其语法为:
log4j.rootLogger = [ level ] , appenderName, appenderName, ...
level 是日志记录的类别
appenderName就是指定日志信息输出到哪个地方。您可以同时指定多个输出目的地。
类别level 为 OFF、FATAL、ERROR、WARN、INFO、DEBUG、log、ALL或自定义的优先级。
og4j常用的优先级FATAL>ERROR>WARN>INFO>DEBUG
配置根Logger,其语法为:
log4j.rootLogger = [ level ] , appenderName, appenderName, …
如果为log4j.rootLogger=WARN,则意味着只有WARN,ERROR,FATAL被输出,DEBUG,INFO将被屏蔽掉。
举例:log4j.rootLogger=INFO,stdout,Runlog,Errorlog
根日志类别为INFO,DEBUG将被屏蔽,其他的将被输出。 stdout,Runlog,Errorlog分别为3个输出目的地。

 

3.3.2        常用输出格式


-X号:X信息输出时左对齐;
%p:日志信息级别
%d{}:日志信息产生时间
%c:日志信息所在地(类名)
%m:产生的日志具体信息
%n:输出日志信息换行
举例:
log4j.appender.stdout.layout.ConversionPattern=%5p%d{yyyy-MM-dd HH:mm:ss}

 

3.3.3        输出布局


使用的输出布局,其中log4j提供4种布局:
org.apache.log4j.HTMLLayout(以HTML表格形式布局)
org.apache.log4j.PatternLayout(可以灵活地指定布局模式),
org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串),
org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)
举例:
输出格式为HTML表格
log4j.appender.stdout.layout=org.apache.log4j.HTMLLayout

 

3.3.4        日志信息的输出目的地


配置日志信息输出目的地Appender,其语法为
log4j.appender.appenderName = fully.qualified.name.of.appender.class
log4j.appender.appenderName.option1 = value1
log4j.appender.appenderName.option = valueN
appenderName就是指定日志信息输出到哪个地方。您可以同时指定多个输出目的地。
log4j支持的输出目的地:
org.apache.log4j.ConsoleAppender 控制台
org.apache.log4j.FileAppender 文件
org.apache.log4j.DailyRollingFileAppender 每天产生一个日志文件
org.apache.log4j.RollingFileAppender (文件大小到达指定尺寸的时候产生一个新的文件),
org.apache.log4j.WriterAppender (将日志信息以流格式发送到任意指定的地方)
org.apache.log4j.net.SMTPAppender 邮件
org.apache.log4j.jdbc.JDBCAppender 数据库
其他如:GUI组件、甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等
举例:
输出到控制台
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender(指定输出到控制台)
log4j.appender.Threshold=DEBUG(指定输出类别)
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout(指定输出布局)
log4j.appender.CONSOLE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n(指定输出格式)
输出到文件
 log4j.appender.FILE=org.apache.log4j.FileAppender(指定输出到文件)
 log4j.appender.FILE.File=file.log(指定输出的路径及文件名)
 log4j.appender.FILE.Append=false
 log4j.appender.FILE.layout=org.apache.log4j.PatternLayout(指定输出的布局)
  log4j.appender.FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n(指定输出的格式)
输出到文件(轮换"日志文件",当日志文件达到指定大小时,该文件就被关闭并备份,然后创建一个新的日志文件)
log4j.appender.ROLLING_FILE=org.apache.log4j.RollingFileAppender(指定输出到文件)
log4j.appender.ROLLING_FILE.Threshold=ERROR(指定输出类别)
log4j.appender.ROLLING_FILE.File=rolling.log(指定输出的路径及文件名)
log4j.appender.ROLLING_FILE.Append=true
log4j.appender.ROLLING_FILE.MaxFileSize=10KB(指定输出到文件的大小)
log4j.appender.ROLLING_FILE.MaxBackupIndex=1
log4j.appender.ROLLING_FILE.layout=org.apache.log4j.PatternLayout(指定采用输出布局)
log4j.appender.ROLLING_FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n(指定采用输出格式)

3.4. 修改日志级别

3.4.1        在程序中修改

private static final Logger log = Logger.getLogger(YouClassName.class);

public static void modify(){

log.setLevel(Level.DEBUG);

}
 

通过Logger的setLevel方法来进行修改

Log4j日志的常用级别按优先级顺序可以分为:

TRACE<DEBUG<INFO<WARN<ERROR<FATAL<OFF

但常用的就只有5个:DEBUG,INFO,WARN,ERROR,FATAL

DEBUG:致命错误

ERROR:错误

WARN:警告

INFO:常用信息

DEBUG:调试信息

TRACE:细节

3.4.2        通过配置文件修改

通过配置文件(log4j.properties)可以配置指定Logger类的日志显示级别,如:

指定com.xx.foo.TestClass类中的日志级别为INFO,输出为stdout定义:

Log4j.logger.com.xx.foo.TestClass = INFO,stdout

指定com.xx.foo包下的所有类的日志级别为DEBUG,输出为stdout定义:

Log4j.logger.com.xx.foo = DEBUG,stdout

名称为IAMLogInfo日志,日志级别为INFO,输出目标为控制台

Log4j.logger.IAMLogInfo = INFO,console

 

 

 

 


一、     概念

 

Commons-logging : apache最早提供的日志的门面接口。避免和具体的日志方案直接耦合。类似于JDBC 的api 接口,具体的的JDBC driver 实现由各数据库提供商实现。通过统一接口解耦,不过其内部也实现了一些简单日志方案。

 

Log4j : 经典的一种日志解决方案。内部把日志系统抽象封装成Logger 、appender 、pattern 等实现。我们可以通过配置文件轻松的实现日志系统的管理和多样化配置。

 

Slf4j : 全称为Simple Logging Facade for JAVA:java简单日志门面。是对不同日志框架提供的一个门面封装。可以在部署的时候不修改任何配置即可接入一种日志实现方案。和commons-loging 应该有一样的初衷。个人感觉设从计上更好一些,没有commons 那么多潜规则。同时有两个额外特点:

 

1.  能支持多个参数,并通过{} 占位符进行替换,避免老写logger.isXXXEnabled 这种无奈的判断,带来性能提升见:http://www.slf4j.org/faq.html#logging_performance

 

2.OSGI 机制更好兼容支持

 

一图胜千言,官网上的一个图:

 

 

 

从上图可以发现,选择还是很多的。

 

Logback : LOGBack 作为一个通用可靠、快速灵活的日志框架,将作为Log4j 的替代和SLF4J 组成新的日志系统的完整实现。官网上称具有极佳的性能,在关键路径上执行速度是log4j 的10 倍,且内存消耗更少。具体优势见:

 

http://logback.qos.ch/reasonsToSwitch.html

 

二、     常见日志方案和注意事项

 

1.Commons-logging+log4j : 经典的一个日志实现方案。出现在各种框架里。如spring 、webx 、ibatis 等等。直接使用log4j 即可满足我们的日志方案。但是一般为了避免直接依赖具体的日志实现,一般都是结合commons-logging 来实现。常见代码如下:

 

import org.apache.commons.logging.Log;

 

import org.apache.commons.logging.LogFactory;

 

private static Log logger = LogFactory.getLog(CommonsLoggingTest.class);

 

代码上,没有依赖任何的log4j 内部的类。那么log4j 是如何被装载的?

 

Log 是一个接口声明。LogFactory 的内部会去装载具体的日志系统,并获得实现该Log 接口的实现类。而内部有一个Log4JLogger 实现类对Log 接口同时内部提供了对log4j logger 的代理。LogFactory 内部装载日志系统流程:

 

1.   首先,寻找org.apache.commons.logging.LogFactory 属性配置

 

2.   否则,利用JDK1.3 开始提供的service 发现机制,会扫描classpah 下的META-INF/services/org.apache.commons.logging.LogFactory 文件,若找到则装载里面的配置,使用里面的配置。

 

3.   否则,从Classpath 里寻找commons-logging.properties ,找到则根据里面的配置加载。

 

4.   否则,使用默认的配置:如果能找到Log4j 则默认使用log4j 实现,如果没有则使用JDK14Logger 实现,再没有则使用commons-logging 内部提供的SimpleLog 实现。

 

从上述加载流程来看,如果没有做任何配置,只要引入了log4j 并在classpath 配置了log4j.xml ,则commons-logging 就会使log4j 使用正常,而代码里不需要依赖任何log4j 的代码。

 

 

 

 

 

2.Commons-logging+log4j+slf4j

 

 

如果在原有commons-logging 系统里,如果要迁移到slf4j, 使用slf4j 替换commons-logging ,也是可以做到的。原理使用到了上述commons-logging 加载的第二点。需要引入Org.slf4j.jcl-over-slf4j-1.5.6.jar 。这个jar 包提供了一个桥接,让底层实现是基于slf4j 。原理是在该jar 包里存放了配置META-INF/services/org.apache.commons.logging.LogFactory =org.apache.commons.logging.impl.SLF4JLogFactory ,而commons-logging 在初始化的时候会找到这个serviceId ,并把它作为LogFactory 。

 

完成桥接后,那么那么简单日志门面SLF4J 内部又是如何来装载合适的log 呢?

 

原理是SLF4J 会在编译时会绑定import org.slf4j.impl.StaticLoggerBinder; 该类里面实现对具体日志方案的绑定接入。任何一种基于slf4j 的实现都要有一个这个类。如:

 

org.slf4j.slf4j-log4j12-1.5.6: 提供对 log4j 的一种适配实现。

 

Org.slf4j.slf4j-simple-1.5.6: 是一种 simple 实现,会将 log 直接打到控制台。

 

……

 

那么这个地方就要注意了:如果有任意两个实现slf4j 的包同时出现,那就有可能酿就悲剧,你可能会发现日志不见了、或都打到控制台了。原因是这两个jar 包里都有各自的org.slf4j.impl.StaticLoggerBinder ,编译时候绑定的是哪个是不确定的。这个地方要特别注意!!出现过几次因为这个导致日志错乱的问题。

 

 

 

 

 

3.Slf4j+logback

 

Slf4j 和log4j 作者都是同一个人。

 

Logback 号称在性能各方面有很多优势,也很诱人。

 

直接使用SLf4j 也很简单:

 

import org.slf4j.Logger;

 import org.slf4j.LoggerFactory;

 

 public class HelloWorld {

   public static void main(String[] args) {

     Logger logger = LoggerFactory.getLogger(HelloWorld.class);

     logger.info("Hello World");

   }

 }

 

代码里也看不到任何具体日志实现方案的痕迹。

 

Logback 没用过,看到过一些诱人介绍。具体大家可以去研究。logback 。

 

注意事项

 

使用日志配置的时候一定要明白需求,同时避免冲突。

如使用SLF4j 的时候为了避免冲突,一定要保障只有一种实现类jar 包在里面。

当遇到日志错乱等问题时,可以从这几个方面来排查

 

你可能感兴趣的:(java)