一、日志概述
1、日志级别
针对不同的场景,日志被分为五种不的级别,按照重要程度依次排序
- DEBUG 级别曰志记录对调试程序有帮助的信息。
- INFO 级别日志 用来记录程序运行现场,虽然此处并未发生错误,但是对排查其他错误具有指导意义。
- WARN 级别日志也可以用来记录程序运行现场,但是更偏向于表明此处有出现潜在错误的可能。
- ERROR 级别日志表明当前程序运行发生了错误,需要被关注。但是当前发生的错误,没有影响系统的继续运行。
- FATAL 级别曰志表明当前程序运行出现了严重的错误事件,并且将会导致应 用程序中断。
2、日志框架
关于日志框架的名词如:log4j、logback、slf4j、jdk-logging、commons-logging,这些指的都是什么,他们之间又有什么关系?如下图,日志框架分为三大部分:日志门面、日志适配器、日志库。利用门面设计模式将接口和实现解耦,使日志使用变得更加简单。
- 日志库
实现了日志的相关功能,主流的日志库有三个,分别是log4j、log-jdk、logback;
其中log4j和logback出自同一个作者,logback是log4j的升级版,且本身实现了slf4j日志门面接口;
log-jdk则是由JDK1.4之后提供的。
由于这两种日志的实现不一样,所以用户使用时需要注意具体细节,这就衍生出了下面的日志门面。 - 日志门面
日志门面只提供一套接口规范,不负责日志功能的实现,目的是让使用者不需要关心底库具体是哪个日志库来负责日志打印和使用细节等。
目前使用最广泛的日志门面有slf4j和common-logging。
门面设计模式是面向对象设计模式中的一种,日志框架就是采用这种模式(类似于JDBC)。 - 日志适配器
门面适配器:
日志门面(slf4j)规范是后来才出现的,因此日志库是没有实现门面接口的,所以要想slf4j+log4j的模式就需一个日志门面适配器(slf4j-log4j12)来解决接口不兼容的问题。
日志库适配器:
一些老的项目中已经直接使用了日志库的API,但是由于打印日志的地方太多,难以改动,所以需要一个适配器来完成从旧日志库的API到门面日志(slf4j)API的路由,这样就可以在不改动原来代码的情况下使用日志门面(slf4j)来统一管理日志,并且也不影响后期日子库的切换。
二、日志库使用
日志门面和日志框架选择:如果是新项目推荐使用logback+slf4j模式,因为logback自身实现了slf4j门面的接口,不需要在引入门面适配器;同时logback是log4j的升级版。具备比log4j更多的优点。如果使用log4j则需要引入对应的门面适配器。本文将会介绍这两种使用方式。
1、slf4j + logback 模式
- 引入依赖
<dependency>
<groupid>org.slf4j</groupid>
<artifactid>slf4j-api</artifactid>
<version>${slf4j-ap vers on}</vers on>
</dependency>
<dependency>
<groupid>ch.qos.logback</groupid>
<artifactd>logback-classic</artifactid>
<version>{logback-class c.version}</version>
</dependency>
<dependency>
<groupid>ch.qos.logback</groupid>
<artifactid>logback-core</artifactd>
<version>${logback-core.version)</version>
</dependency>
- 日志配置文件:logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- Logback Configuration. -->
<configuration debug="false" packagingData="${log.packagingData}">
<!-- 1、引入.properties,可以使用其中的配置项 -->
<property resource="application.properties"/>
<!-- 2、控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!-- 1格式化输出:日志格式具体参见另一篇博文-->
<pattern>
[%X{requestId}] %d{yy/MM/dd HH:mm:ss.SSS} %green(%-5level) [%-15.15thread] %X{EAGLEEYE_TRACE_ID}
%X{EAGLEEYE_RPC_ID} %cyan(%-35.35logger{34}) - %msg%n
</pattern>
<charset>utf-8</charset>
</encoder>
</appender>
<!-- 3、日志输出级别:日志级别来自于配置文件 -->
<root level="${log.level}">
<appender-ref ref="STDOUT" />
</root>
</configuration>
- 加载日志配置文件
如果文件命名为logback-spring.xml或者logback.xml则将该文件放在resource根目录下框架就可以自动识别。其他命名则需要显示的在application.properties文件中指定文件位置:logging.config=classpath:log/logback-spring.xml
配置文件命名:
官方推荐命名为logback-spring.xml,因为以-spring结尾的配置文件可以使用springProfile标签,该标签可以使用环境变量。当然可以自定义其他名称。其他名称则需和上面一样在配置文件中配置文件路径。
- 使用日志框架打印日志
获取日志对象的工厂LoggerFactory应该是日志门面slf4j中的org.slf4j包下
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger= LoggerFactory.getLogger(Abc.class);
2、slf4j + slf4j-log4j12 + log4j 模式(后期补充)
三、日志框架使用规范
1、日志文件命名
- 日志文件命名依据
要清清除的知道日志文件属于什么应用,什么类型,什么目的,有利于归类和查找。 - 命名推荐
应用名称_日志类型_日志文件名称.log(appName_logType_logName.log)
日志类型推荐:stats状态,monitor监控,visit访问等
日志文件名称:日志内容的描述
2、 日志保留期限
日志保留时长过长,会对磁盘存储造成较大压力,保存太短又不利于发现针对周为频次(个人理解为迭代周期)发生的异常,所以推荐日志至少保留15天,当然根据日志的用途和重要程度可以适当延长保存期限。
3、日志内容
- 日志输出的标准
日志使用来快速定位问题的,但是并非多多益善,过多会既降低系统性能,所以一定要思考:这条日志是否有人看;看到这条日志能做什么;能不能提升问题排查效率。 - 日志输出方式
考虑到系统运行效率和日志打印需求,针对DEBUG和INFO级别的日志,应该使用占位符的方式打印,否则就是用条件判断的方式打印,因为不是用占位符而使用了字符串拼接,即使不打印日志系统也会执行字符串拼接工作,从而白白浪费了系统资源。
//使用占位符{}形式:推荐使用这种方式
logger.debug ("Processing trade with iJ: {} and nbol{}",id, synbol)
//使明条件判断形式:不推荐使用,因为如果不加if判断语句,logger日志在任何日志级别下都会字符串拼接,即使不输入日志。
if (logger.isDebugEnabled()) {
logger.debug (” Processing trade with id:” + id + ” and symbol : " + symbol) ;
}
- 日志输出级别
生产环境禁止输出DEBUG日志,有选择的输出INFO日志,并且注意日志输出量,通过设置合理日志文件生命周期,即使清理日志,避免磁盘空间不足。 - 避免重复打印
避免重复打印,务必在配置文件中设置additivity=false:
<logger name="com.taobao.ecrm.member.config" additivity="false">
- 区分错误日志
ERROR级别的日志,应该只记录系统逻辑错误、异常或者违反业务规则的日志,这些错误一旦发生就需要人工接入;否则就应该记录WARN日志,如用户输入了错误的参数导致异常,这时候应该记录WARN级别日志,目的是在用户咨询时可以还原现场情况。 - 保证记录的完整性(现场的上下文信息,异常的堆栈信息)
日志中如果输出对象实例,要确保实例重写了toString()方法,否则只会打印出实例的hashCode值,没有意义。
记录异常时一定要输出异常的堆栈信息
logger.error("parameters:{}",parameter,e)