java的log框架全介绍

1.本篇不是什么?

不会告诉你应用系统为什么要引入日志框架?
不会告诉你系统中什么样的异常及错误信息应该记录日志?
不会告诉你怎么可以高效的记录准确(能清晰的反应系统异常或业务异常)的日志?
当然也不会告诉你记录日志的n最佳实践,这些会在后一篇文章中详细介绍的.... .
   2.java中主要的日志框架有哪些?
 java.util.Logging(JUL)
Java Logging API是sun公司于2002年5月正式发布的。它是自J2SE 1.4版本开始提供的一个新的应用程序接口。它能够很方便地控制和输出日志信息到文件,控制台或其它用户定义的地方,如数据库,电子邮件等。所以它是为最 终用户,系统管理员,软件服务工程师和开发人员提供的一种捕捉安全漏洞,检查配置正确性,跟踪调查系统运行瓶颈和调查系统运行错误的工具。 Java Logging API提供了七个级别用来控制输出。从高到底分别是:finest,finer,fine,config,info,warning,server
 
Log4j
Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件、甚至是套接口服务 器、NT的事件记录器、UNIX Syslog守护进程等;用户也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,用户能够更加细致地控制日志的生成过程。这些可以通过一个 配置文件来灵活地进行配置,而不需要修改程序代码。配置文件log4j.properties or log4j.xml,Logger的由低到高级别如下: ALL<DEBUG<INFO<WARN<ERROR<FATAL<OFF 
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。
 
以上都是日志的具体实现方式,我们编写代码的时候一般都不会是直接和这些API交互,而是与下面两个日志的接口(门面)库打交道。
  A pache Common-Logging (JCL)  
目前广泛使用的Java日志门面库。通过动态查找的机制(后面有代码介绍),在程序运行时自动找出真正使用的日志库。但由于它使用了ClassLoader寻找和载入底层的日志库, 导致了象OSGI这样的框架无法正常工作,由于其不同的插件使用自己的ClassLoader。 OSGI的这种机制保证了插件互相独立,然而却使Apache Common-Logging无法工作
 SLF4J 
简单日记门面(Facade)SLF4J是为各种loging APIs提供一个简单统一的接口,从而使得最终用户能够在部署的时候配置自己希望的loging APIs实现。 Logging API实现既可以选择直接实现SLF4J接的loging APIs如: NLOG4J、SimpleLogger。也可以通过SLF4J提供的API实现来开发相应的适配器如Log4jLoggerAdapter、JDK14LoggerAdapter。 
  他们之间的相互比较:
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 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是比较好的日志框架选型
 
 
3.在开发的时候如何组织这些日志框架为我所用?
 
下面这个是比较经典的组合,也是比较趋于理想化的,实际项目中可能会复杂一些。
 

日志组合

JCL+Log4j

SLF4J+Logback

优点

l使用的成熟

l案例丰富

l越来越多选择其作为日志方案,如Hibernate、iBatis

l日志性能高,支持参数化的log字符串,避免了之前为了减少字符串拼接的性能损耗而不得不写的if(logger.isDebugEnable()),现在你可以直接写:logger.debug(“current user is: {}”, user)

缺点

l不再被维护

l不适用OSGi开发

l日志记录性能不高

lLogback作为Log4j的继任者

lLogback兼容Log4j,但提供更多的扩展

目前比较推荐的是后一种,但是老的项目基本上都是前一种。对于后一种组合,如果引用第三方类库时,第三方类库使用了JUL,JCL或者log4j,则需要引入对应的jul-to-slf4j.jar,jcl-over-slf4j.jar,log4j-over-slf4j.jar进行适配,为什么要引入这些,后面后代码说明。下面上一张神图,看过之后,就会明白我写的文字多苍白。


 
4.JCL日志门面动态查找日志框架的源码如下:
 static {
 
        // Is Log4J Available?
        try {
            if (null != Class.forName("org.apache.log4j.Logger")) {
                log4jIsAvailable = true;
            } else {
                log4jIsAvailable = false;
            }
        } catch (Throwable t) {
            log4jIsAvailable = false;
        }
 
        // Is JDK 1.4 Logging Available?
        try {
            if ((null != Class.forName("java.util.logging.Logger")) &&
                (null != Class.forName("org.apache.commons.logging.impl.Jdk14Logger"))) {
                jdk14IsAvailable = true;
            } else {
                jdk14IsAvailable = false;
            }
        } catch (Throwable t) {
            jdk14IsAvailable = false;
        }
 
        // Set the default Log implementation
        String name = null;
        try {
            name = System.getProperty("org.apache.commons.logging.log");
            if (name == null) {
                name = System.getProperty("org.apache.commons.logging.Log");
            }
        } catch (Throwable t) {
        }
        if (name != null) {
            try {
                setLogImplementation(name);
            } catch (Throwable t) {
                try {
                    setLogImplementation
                            ("org.apache.commons.logging.impl.NoOpLog");
                } catch (Throwable u) {
                    ;
                }
            }
        } else {
            try {
                if (log4jIsAvailable) {
                    setLogImplementation
                            ("org.apache.commons.logging.impl.Log4JLogger");
                } else if (jdk14IsAvailable) {
                    setLogImplementation
                            ("org.apache.commons.logging.impl.Jdk14Logger");
                } else {
                    setLogImplementation
                            ("org.apache.commons.logging.impl.NoOpLog");
                }
            } catch (Throwable t) {
                try {
                    setLogImplementation
                            ("org.apache.commons.logging.impl.NoOpLog");
                } catch (Throwable u) {
                    ;
                }
            }
        }
 
    }
  默认会先找Log4j,其次是JUL再次是JCL自己的,不过此时没有任何操作。该类的源码放在 : https://gist.github.com/yongchun/7595536
5.日志适配代码分析
logback日志框架完全实现了slf4j,而其他的日志框架JUL,log4j(完全实现了JCL)则不是的。那么对于抽象的slf4j日志门面要想使用这些日志框架就需要做些适配。
下面以日志的一个级别warn,说明其在各个日志框架中是怎样定义和实现的。
 
JCL中,定义的接口为
org.apache.commons.logging.Log

 public void warn(Object message);

 public void warn(Object message, Throwable t);
  其自身的实现为:
  public void warn(Object message) {
        getLogger().log(FQCN, Priority.WARN, message, null );
    }

    public void warn(Object message, Throwable t) {
        getLogger().log(FQCN, Priority.WARN, message, t );
    }

 log4j对应的实现为:
org.apache.log4j.Logger
 public
  void warn(Object message) {
    if(repository.isDisabled( Level.WARN_INT))
      return;

    if(Level.WARN.isGreaterOrEqual(this.getEffectiveLevel()))
      forcedLog(FQCN, Level.WARN, message, null);
  }


  public
  void warn(Object message, Throwable t) {
    if(repository.isDisabled(Level.WARN_INT))
      return;
    if(Level.WARN.isGreaterOrEqual(this.getEffectiveLevel()))
      forcedLog(FQCN, Level.WARN, message, t);
  }
}
 
其中JCL里的 org.apache.commons.logging.Log.JDK14Logger将JUL的日志级别进行了转换,使其对应到JCL的日志级别。 使用JCL可以天然的使用JUL和log4j,因为他们的方法签名是一样的,完全是接口和接口对应的实现嘛。
 
而SLF4J,定义的接口为
 
 public void warn(String msg);

  public void warn(String format, Object arg);

  public void warn(String format, Object... arguments);

  public void warn(String format, Object arg1, Object arg2);

   public void warn (String msg, Throwable t);
 
logback日志框架的实现

 

 public void warn (String msg) {
    filterAndLog_0_Or3Plus(FQCN, null, Level. WARN, msg, null, null );
  }

  public void warn(String msg, Throwable t) {
    filterAndLog_0_Or3Plus(FQCN, null, Level. WARN, msg, null, t);
  }

  public void warn(String format, Object arg) {
    filterAndLog_1(FQCN, null, Level. WARN, format, arg, null);
  }

  public void warn(String format, Object arg1, Object arg2) {
    filterAndLog_2(FQCN, null, Level. WARN, format, arg1, arg2, null);
  }

  public void warn(String format, Object[] argArray) {
    filterAndLog_0_Or3Plus(FQCN, null, Level. WARN, format, argArray, null);
  }

  public void warn(Marker marker, String msg) {
    filterAndLog_0_Or3Plus(FQCN, marker, Level. WARN, msg, null, null );
  }

  public void warn(Marker marker, String format, Object arg) {
    filterAndLog_1(FQCN, marker, Level. WARN, format, arg, null);
  }

  public void warn(Marker marker, String format, Object[] argArray) {
    filterAndLog_0_Or3Plus(FQCN, marker, Level. WARN, format, argArray, null);
  }

  public void warn(Marker marker, String format, Object arg1, Object arg2) {
    filterAndLog_2(FQCN, marker, Level. WARN, format, arg1, arg2, null);
  }

  public void warn(Marker marker, String msg, Throwable t) {
    filterAndLog_0_Or3Plus(FQCN, marker, Level. WARN, msg, null, t);
  }

 

所以slf4j和logback是一套的,是接口和接口实现的关系。
再看下JCL-->slf4j  的转换 jcl-over-slf4j.jar

 

 public void warn(Object message) {
        differentiatedLog(null, CATEGORY_FQCN, LocationAwareLogger.WARN_INT,
                message, null);
    }


    public void warn(Object message, Throwable t) {
        differentiatedLog(null, CATEGORY_FQCN, LocationAwareLogger.WARN_INT,
                message, t);
    } 


  void differentiatedLog(Marker marker, String fqcn, int level, Object message,
            Throwable t) {

        String m = convertToString(message);
        if (locationAwareLogger != null) {
            locationAwareLogger.log(marker, fqcn, level, m, null, t);
        } else {
            switch (level) {
                case LocationAwareLogger.TRACE_INT:
                    slf4jLogger.trace(marker, m);
                    break;
                case LocationAwareLogger.DEBUG_INT:
                    slf4jLogger.debug(marker, m);
                    break;
                case LocationAwareLogger.INFO_INT:
                    slf4jLogger.info(marker, m);
                    break;
                case LocationAwareLogger.WARN_INT:
                    slf4jLogger.warn(marker, m);
                    break;
                case LocationAwareLogger.ERROR_INT:
                    slf4jLogger.error(marker, m);
                    break;
            }
        }
    }

 

  其中:

 locationAwareLogger.log(marker, fqcn, level, m, null, t); 

 

public void log(Marker marker, String fqcn, int level, String message, Object[] argArray, Throwable t);

 这个方法是slf4j的spi

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值