背景

之前在写代码时也会使用日志机制,便于程序的调试和输出显示,但是一直默认的使用的时候debug模式,昨天开例会,有同事提到了日志级别,发现自己对这块一直挺模糊的,今天就来罗列和总结一下。以前也有同事分享过相关的知识,特意找了下当时的文档,也参考了一下。

日志级别介绍

日志的级别有很多,我们一般只用四个。日志级别由低到高DEBUG - INFO - WARN - ERROR;

  • DEBUG(调试):开发调试日志。一般来说,在系统实际运行过程中,不会输出该级别的日志。因此,开发人员可以打印任何自己觉得有利于了解系统运行状态的东东。不过很多场景下,过多的DEBUG日志,并不是好事,建议是按照业务逻辑的走向打印。打个比方,打印日志就像读书时划重点,如果导出都是重点,也就失去了重点。
  • INFO(通知):INFO日志级别主要用于记录系统运行状态等关联信息。该日志级别,常用于反馈系统当前状态给最终用户。所以,在这里输出的信息,应该对最终用户具有实际意义,也就是最终用户要能够看得明白是什么意思才行。
  • WARN(警告):WARN日志常用来表示系统模块发生问题,但并不影响系统运行。 此时,进行一些修复性的工作,还能把系统恢复到正常的状态。
  • ERROR(错误):此信息输出后,主体系统核心模块正常工作,需要修复才能正常工作。 就是说可以进行一些修复性的工作,但无法确定系统会正常的工作下去,系统在以后的某个阶段,很可能会因为当前的这个问题,导致一个无法修复的错误(例如宕机),但也可能一直工作到停止也不出现严重问题。

日志打印规范

  • 禁止使用性能很低的System.out()打印日志信息。
  • 禁止e.printStackTrace();logger.error(e.getMessage())
  • 对于DEBUG、INFO级别的日志,必须使用条件输出或者使用占位符的方式打印。
# 配置日志级别为WARN的应用中,针对DEBUG级别的日志,仅仅在程序中写出

logger.debug("Procession trade with id : "+id+" and user: "+user);

# 那么此日志不会被打印,但是会执行字符串拼接操作,如果user是对象,还会执行toString()方法,白白浪费系统资源。

# 正确打印日志的方式:
# 条件判断形式
if(logger.isDebugEnabled()){
    logger.debug("Procession trade with id : "+id+" and user: "+user);
}
# 使用占位符形式
logger.debug("Procession trade with id : {} and user: {}", id, user);
  • 保证记录内容完整。日志记录的内容要包括上下文信息与异常堆栈信息。
  • 错误示范:logger.error("xxxxx",e.getMessage());。正确示范:logger.error("xxxxx", e);
  • 日志中如果输出对象实例,要确保实例重写了toString()方法,否则只会输出对象的hashCode值,没有实际意义。
  • 使用日志框架而不是直接使用日志库
  • 尽量使用异步日志(TODO研究)
  • 记录外部输入参数错误的情况,使用warn级别而不是error级别。(通过重新输入参数可以避免错误,非系统error)

避免频繁报警。一些重要的错误可以和报警系统关联起来

通过线上测试酌情考虑根据文件大小滚动文件。按日分割?按小时分割?按大小分割?

综上所述,日志是一个系统必不可少的组成部分,但是日志打印并非多多益善,过多的日志会降低系统的性能,也不利于快速定位问题,所以记录日志时一定要思考三个问题:①日志是否有人看;②看到这条日志能做什么;③能不能提升问题排查效率。

日志框架

log4j、logback、slf4j、commons-logging…

基础知识
  • 日志框架分为三大部分,包括日志门面、日志适配器、日志库。
  • 日志门面:门面设计模式是面向对象设计模式中的一种。日志门面采用的就是这种模式。它只提供一套接口规范,自身不负责日志功能实现,目的是让使用者不需要关注底层具体是哪个日志库来负责日志打印以及具体的使用细节。目前最为广泛的日志门面有两种:slf4j和common-logging。

Simple Logging Facade for Java

门面设计模式:提供一个统一的接口去访问多个子系统的多个不同的接口,它为子系统中的一组接口提供一个统一的高层接口。使用子系统更容易使用。

  • 日志库:它具体实现了日志的相关功能,主流的日志库有三个,分别是log4j、log-jdk、logback。
  • log4j是最早诞生的日志库,它诞生之前都是使用system.out或者system.err。
  • jdk1.4版本引入了java.util.logging.Logger,简称log-jdk。
  • logback是最晚出现的,它与log4j出自同一个作者,是log4j升级版,而且本身就实现了slf4j的接口。
  • 日志适配器
  • 日志门面适配器:因为slf4j规范是后来提出来的,在此之前的日志库是没有实现slf4j的接口的,例如:log4j,所以,在工程里要想使用slf4j+log4j的模式,就额外需要一个适配器(slf4j-log4j12)来解决接口不兼容的问题。
  • 日志库适配器:在一些老的工程里面,一开始为了开发简单而直接使用了日志库API来完成日志打印,随着时间推移想将原来直接调用日志库的模式改为业界标准的门面模式(例如:slf4j+logback组合),但老工程代码里打印日志地方太多,难以改动,所以需要一个适配器来完成从旧日志库API到slf4j的路由,这样不改动原有代码的情况下也能使用slf4j来统一管理日志,而且后续自由替换具体日志库也不成问题。
推荐使用
新工程
  • 如果是新工程,推荐使用slf4j+logback模式。因为logback自身实现了slf4j的接口,无需额外引入适配器,另外logback是log4j升级版,具备比log4j更多的优点。

logback当前分成三个模块:logback-core,logback-classic和logback-access。logback-core是其它两个模块的基础模块

<dependencies>

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>${slf4j-api.version}</version>
    </dependency>

    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>${logback-classic.version}</version>
    </dependency>

    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-core</artifactId>
        <version>${logback-core.version}</version>
    </dependency>
</dependencies>
  • classpath中编写logback的配置文件logback.xml, 以maven项目为例,logback.xml文件放在/src/main/resources/路径中
老工程
  • 如果是老工程,则需要根据所使用的日志库来确定门面适配器,通常情况下老工程使用的是log4j,因此以log4j日志库为例,进行配置:
<dependencies>

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>${slf4j-api.version}</version>
    </dependency>

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>${slf4j-log4j12.version}</version>
    </dependency>
    
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>${log4j.version}</version>
    </dependency>

</dependencies>
  • 如果老代码中直接使用了log4j日志库提供的接口来打印日志,则还需要引入日志库适配器:
<dependencies>

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>log4j-over-slf4j</artifactId>
        <version>${log4j-over-slf4j.version}</version>
    </dependency>

</dependencies>
实际使用
  • private static final Logger logger = LoggerFactory.getLogger(A.class);注意,logger被定义为static变量,是因为这个logger与当前类绑定,避免每次都new一个新对象,造成资源浪费。
  • 在使用slf4j+日志库模式时,要防止日志库冲突,一旦发生则可能会出现日志打印失效的问题。jar包冲突可能导致应用无法启动。(实际遇到过)
# springboot项目中:

	<!-- 大数据平台上下传文件 -->
		<dependency>
			<groupId>cn.onebank.bigdata.HdfsClient</groupId>
			<artifactId>HdfsClientApi</artifactId>
			<version>0.0.7</version>
			<exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
		</dependency>

Java常用的日志框架对比
最佳日志实践
Java日志最佳实践