Java日志框架

Java日志框架

一、门面模式

门面模式

在上面的例子当中其实我们发现,整个门面模式分成了两个部分,有三个角色:

  • 第一部分:

    客户类(client),也就是张三。

  • 第二部分:

    门面角色(facade):在这里指的是包工头。客户端可以调用这个角色的方法。此角色知晓相关的(一个或者多个)子系统的功能和责任。在这里指的是,张三可以调用包工头,包工头也需要知道其手下搬砖、砌墙、和泥等人。

    子系统角色(subSystem):在这里指的就是会和泥的、会搬砖的、会砌墙的人。当然,每个子系统都可以被客户端直接调用,或者被门面角色调用。子系统并不知道门面的存在,对于子系统而言,门面仅仅是另外一个客户端而已。

二、日志框架

日志框架

日志框架分为三大部分,包括日志门面、日志适配器、日志库。利用门面设计模式,即Facade进行解耦,使日志使用变得更加简单。

2.1、日志门面

日志门面采用门面设计模式提供了一套接口规范,自身不负责日志功能的实现,目的是让使用者不需要关注底层具体是哪个日志库来负责日志打印及具体使用细节。比较常用的日志门面有两种:slf4j和 commons-logging

2.2、日志库

负责实现日志相关功能,主流日志库有三个,分别为:log4j、log-jdk(java.util.logging.Logger)、logback。logback是最晚出现的,与log4j同一个作者,是log4j的升级版且本身实现了slf4j的接口。

2.3、日志适配器

分为:

  • 日志门面适配器(日志库适配slf4j),日志库适配器(slf4j适配日志库)。

  • 日志门面适配器:老工程用的日志库没有实现slf4j接口,如log4j;这时候工程里想使用slf4j+log4j的模式,就额外需要一个适配器(slf4j+log4j12)来解决接口不兼容问题。

  • 日志库适配器:老工程直接使用日志库API完成日志打印,要改成业界标准的门面模式(如slf4j+logback),但是老工程代码打印日志地方太多难以改动,这时就需要一个适配器来完成从旧日志库的API到slf4j的路由,这样在不改动原有代码的情况下也能使用slf4j来统一管理日志(如:log4j-over-slf4j),后续自由替换具体日志库也不成问题。

2.4、几种常见的日志概念

slf4j【日志门面】

全称为Simple Logging Facade for JAVA:java简单日志门面。 是对不同日志框架提供的一个门面封装。可以在部署的时候不修改任何配置即可接入一种日志实现方案。但是,他在编译时静态绑定真正的Log库。使用SLF4J时,如果你需要使用某一种日志实现,那么你必须选择正确的SLF4J的jar包的集合(各种桥接包)。

官方给的日志

这种思想随处可见,比如java提供的JDBC接口屏蔽了各个数据库厂商数据库驱动的差异,JVM屏蔽了各个操作系统的差异;而现在日志框架接口也干了同样的事,屏蔽了各个日志框架的差异。在开发中我们的应用只依赖抽象,不强依赖具体的实现;这样做的好处是我们替换具体日志框架的实现的时候,不需要改动我们的代码。我们可以对上面的图再简化一下。

日志框架jar包之间的关系

1.	import org.slf4j.Logger;    
2.	import org.slf4j.LoggerFactory;    
3.	    
4.	public class Test {    
5.	    private static Logger logger = LogFactory.getLog(Test.class);    
6.	}
slf4j静态绑定原理

SLF4J 会在编译时会绑定 import org.slf4j.impl.StaticLoggerBinder; 该类里面实现对具体日志方案的绑定接入。任何一种基于slf4j 的实现都要有一个这个类。如:org.slf4j.slf4j-log4j12-1.5.6: 提供对 log4j 的一种适配实现。注意:如果有任意两个实现slf4j 的包同时出现,那么就可能出现问题。

commons-logging【日志门面】

apache最早提供的日志的门面接口。避免和具体的日志方案直接耦合,户可以自由选择第三方的日志组件作为具体实现,像log4j,或者jdk自带的logging, commons-logging会通过动态查找的机制,在程序运行时自动找出真正使用的日志库。当然,commons-logging内部有一个Simple logger的简单实现,但是功能很弱。

    import org.apache.commons.logging.Log;    
2.	import org.apache.commons.logging.LogFactory;    
3.	    
4.	public class A {    
5.	    private static Log logger = LogFactory.getLog(this.getClass());    
6.	} 
commons-logging动态查找原理

Log 是一个接口声明。LogFactory 的内部会去装载具体的日志系统,并获得实现该Log 接口的实现类。LogFactory 内部装载日志系统的流程如下:

  • 首先,寻找org.apache.commons.logging.LogFactory 属性配置。
  • 否则,利用JDK1.3 开始提供的service 发现机制,会扫描classpath 下的META-INF/services/org.apache.commons.logging.LogFactory文件,若找到则装载里面的配置,使用里面的配置。
  • 否则,从Classpath 里寻找commons-logging.properties ,找到则根据里面的配置加载。
  • 否则,使用默认的配置:如果能找到Log4j 则默认使用log4j 实现,如果没有则使用JDK14Logger 实现,再没有则使用commons-logging 内部提供的SimpleLog 实现。

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

Log4j【日志实现】

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

Logback【日志实现】

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

三、slf4j和commons-logging对比

commons-logging通过动态查找的机制,在程序运行时自动找出真正使用的日志库。由于它使用了使用了Thread的ClassLoader寻找和载入底层的日志库, 导致了象OSGI这样的框架无法正常工作,因为OSGI的不同的插件使用自己的ClassLoader。 OSGI的这种机制保证了插件互相独立,然而却使Apache Commons-Logging无法工作。

这块多说一点,传统的Java大多是一个classloader跑到底,所以这些logging api都没什么问题,都很棒。(Java通过Classloader加载Class,Classloader之间相互隔离。隔离真正的意思是:不同的Classloader可以加载相同的class定义,并且被jvm认定为不同的class。)但是当单classloader变成多classloader的时候,程序模块化了(OSGI中,每一个Bundle有一个单独的Classloader实例。OSGI不知道?想想Eclipse的核心技术,想想你是怎么装插件的),所以一听commons-logging用到了ClassLoader,就知道正常情况下无解。(这个说的是统一服务,要是把logging当成局部服务,每个Bundle都带自己的logging api,当成单classloader用也没啥问题,就是各种logging api,一堆重复导入的配置,让人烦不胜烦)

slf4j在编译时静态绑定真正的Log库,因此可以再OSGI中使用。另外,SLF4J 支持参数化的log字符串,避免了之前为了减少字符串拼接的性能损耗而不得不写的if(logger.isDebugEnable()),现在你可以直接写:logger.debug(“current user is: {}”, user)。拼装消息被推迟到了它能够确定是不是要显示这条消息的时候,但是获取参数的代价并没有幸免。Slf4j官方解释

四、常见日志方案

4.1、Commons-logging+log4j

经典的日志实现方案,出现在各种框架中。咱们公司内部老系统一般都是用这个记录日志(不排除有直接用log4j的系统)。

4.2、Commons-logging+log4j+slf4j

在原有commons-logging 系统里,如想迁移到slf4j, 使用slf4j 替换commons-logging ,也是可以做到的。需要引入jcl-over-slf4j。这个jar包提供了一个桥接,让日志底层实现变为基于slf4j。
原理是在该jar 包里存放了配置META-INF/services/org.apache.commons.logging.LogFactory =org.apache.commons.logging.impl.SLF4JLogFactory ,而commons-logging 在初始化的时候会找到这个serviceId ,并把它作为LogFactory 。完成桥接后,slf4j会在编译时绑定org.slf4j.impl.StaticLoggerBinder, 该类里面实现对具体日志方案的绑定接入。这个就可以参考上面那个图了。

4.3、Slf4j+logback

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

五、日志打印规范

5.1、日志级别

  • DEBUG级别: 记录对调试程序有帮助的信息,即程序内部运行的信息,对外部使用的人是没有意义的。比如:方法的执行时间。注:开发环境是不允许有这部分日志的!
  • INFO级别:记录程序运行现场,强调应用的执行的进度,关键分支的记录。比如:关键分支记录(输入参数等),指明程序的运行是否符合正确的业务逻辑。虽然此处并未发送错误,但是对排查其他错误信息具有指导意义。
  • WARN级别:记录程序运行现场,但是更偏向于表明此处有出现潜在错误的可能。
  • ERROR级别:记录当前程序发生的错误,需要被关注。但是当前发送的错误,没有影响系统继续运行。
  • FATAL级别:记录当前程序运行出现严重错误事件,并且将会导致应用程序中断。

5.2、WARN和ERROR的选择

日志级别说明描述
WARN警告预期之外的运行状况,可能会出现潜在错误的情形,比如大量时延过大等;一般是由系统资源等技术原因触发。
ERROR错误错误事件,影响正常使用,但仍然不影响系统的继续运行。

5.3、日志规范

预先判断日志级别

对于DEBUG、INFO级别日志,必须使用条件输出或者使用占位符的方式打印。

//字符串拼接方式(错误)
logger.debug("Application is start, startTime:" + System.currentTimeMillis());
//使用条件判断方式(正确)
if (logger.isDebugEnabled()) {
    logger.debug("Application is start, startTime:" + System.currentTimeMillis());
}
//占位符方式(正确)
logger.debug("Application is start, startTime:" + System.currentTimeMillis());

避免无效日志打印

生产环境是禁止DEBUG日志且有选择地输出INFO日志。使用INFO、WARN级别来记录业务行为信息时,一定要控制日志输出量,以免出现磁盘空间不足。同时要为日志设置合理的生命周期,及时清理过期日志。避免重复打印,务必在日志配置文件中设置additivity=false。

区别对待错误日志

WARN日志:记录一些业务异常可以通过引导重试就能恢复正常的日志信息,如用户输入参数错误。ERROR日志:记录系统逻辑错误、异常或违法重要业务规则的日志信息(需要人工干预)。

保证记录内容完整

日志记录的内容包括现场上下文信息与异常堆栈信息。打印要注意以下几点:

  • 记录异常时一定要输出异常堆栈。
Logger.error(“xxx:{}, param, e);
  • 日志中如果输出对象实例,要确保实例类重写了toString方法,否则只会输出对象的hashcode值。
  • 如果有多个方法调用一个方法的情况,一定要记录上下文信息,比如是哪个方法调用的,或者在调用入口处用info打印一下入口信息等。
  • 日志中不要记录对象.xxx这种形式,如果对象为null,日志打印时一样会抛出异常,无法记录程序关键信息。
  • 使用slf4j,一律用占位符输出关键变量,如
Logger.error(“描述信息:{}, 一些唯一标识, e);

不使用slf4j,请用条件判断的形式,打印日志,如

if (logger.isDebugEnabled()) {
   Logger.debug(“解析xml方法运行了:” + xxx + “秒”);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值