日志系统概述
日志可以帮助我们快速的定位问题,记录程序运行过程中的情况,以便项目的监控和优化。我们在日常开发中主要使用的java日志框架组件是:log4j、SLF4J、Commons Logging
日志系统由三个重要的组件构成:日志信息的优先级,日志信息的输出目的地,日志信息的输出格式。这些信息可以在 properties的文件中进行配置
一般来说日志分为两种:业务日志和异常日志
slf4j-api、slf4j-log4j12以及log4j之间的关系
slf4j入口就是众多接口的集合,它不负责具体的日志实现,只在编译时负责寻找合适的日志系统进行绑定。它只服务于各种各样的日志系统,具体有哪些接口,全部都定义在slf4j-api中。
slf4j-log4j12是链接slf4j-api和log4j中间的适配器。
log4j是具体的日志系统。通过slf4j-log4j12初始化Log4j,达到最终日志的输出。
Apache common logging
common logging和slf4j是相同的功能,都是负责寻找合适的日志系统进行绑定
日志级别
日志级别一般分为7个打印级别:
- OFF-关闭所有日志;
- FATAL-记录严重的错误,并且会导致应用程序退出;
- ERROR-记录严重的错误,但不会影响程序的运行;
- WARN-记录警告;
- INFO-记录程序中比较有意义的信息;
- DEBUG-记录程序中的细节信息;
- ALL-记录所有的日志;
logback
logback同样是由log4j的作者设计完成的,拥有更好的特性,用来取代log4j的一个日志框架。是slf4j的原生实现。
logback是直接实现了slf4j的接口,而log4j不是对slf4j的原生实现,所以slf4j api在调用log4j时需要一个适配层。
日志系统使用规范
采用slf4j作为日志API,采用logback作为日志输出工具,用slf4j桥接方式替换掉log4j和commons-logging。
特殊的记录,需要大批量写入日志文件,应该采用异步线程写文件。
各级别日志使用原则
1、fatal(致命错误)使用原则
fatal为系统级别的异常,发生fatal错误,代表服务器整个或者核心功能已经无法工作了!!
1)在服务器启动时就应该检查,如果存在致命错误,直接抛异常,让服务器不要启动起来(启动了也无法正常工作,不如不启动)。
- 如果在服务器启动之后,发生了致命的错误,则记录fatal级别的错误日志,最好是同时触发相关的修复和告警工作(比如,给开发和维护人员发送告警邮件)。
2、error(错误)使用原则
error为功能或者逻辑级别的严重异常,发生error级别的异常,代表功能或者重要逻辑遇到问题、无法正常工作。
3、warn(警告)使用原则
warn用在某些逻辑非常规,发生了一些小故障但是没有大的影响,或者重要数据被修改,或者某些操作需要引起重视。
4、info(信息)使用原则
info用于记录一些有用的、关键的信息,一般这些信息出现得不频繁,只是在初始化的地方或者重要操作的地方才记录。
5、debug(调试)使用原则
debug用于记录一些调试信息,为了方便查看程序的执行过程和相关数据、了解程序的动态。
6、trace(跟踪)使用原则
trace用于记录一些更详细的调试信息,这些信息无需每次调试时都打印出来,只在需要更详细的调试信息时才开启。
项目稳定运行时的日志量
1)正常情况下,trace日志至少是debug日志的100倍,
trace级别的日志量 : debug级别的日志量 > 100 : 1,
也就是说trace日志非常多,debug日志相对较少。
2)debug级别的日志量 : info级别的日志量 > 1000 : 1,
也就是说正常情况下,info日志很少,只在部分重要位置会输出 info日志。
error日志和warn日志,正常情况下,几乎为0,当出现异常时,error日志和warn日志量 也在可控范围,不会超过debug级别的最大日志量。
各环境默认日志定义
开发环境
1)默认日志级别定义为:
app包为TRACE级别。日志的ROOT Level为DEBUG级别。
2)
启用 System.out 控制台输出日志;
启用error.log为错误和警告日志、app.log为应用日志(包括app包下的日志和其他INFO级别以上的日志)。
测试环境
1)默认日志级别定义为:
app包为DEBUG级别。日志的ROOT Level为DEBUG级别。
2)
禁用 System.out 控制台输出日志;
启用error.log为错误和警告日志、app.log为应用日志(包括app包下的日志和其他INFO级别以上的日志)。
生产环境
1)默认日志级别定义为:
app包为DEBUG级别。日志的ROOT Level为INFO级别。
2)
禁用 System.out 控制台输出日志;
启用error.log为错误和警告日志、app.log为应用日志(包括app包下的日志和其他INFO级别以上的日志)。
基本的Logger编码规范
- 在一个对象中通常只使用一个Logger对象,Logger应该是static final的,只有在少数需要在构造函数中传递logger的情况下才使用private final。
- 输出Exceptions的全部Throwable信息,因为logger.error(msg)和logger.error(msg,e.getMessage())这样的日志输出方法会丢失掉最重要的StackTrace信息。
- 不允许记录日志后又抛出异常,因为这样会多次记录日志,只允许记录一次日志。
- 不允许出现System print(包括out.println和System.error.println)语句。
- 不允许出现printStackTrace。
日志性能的考虑,如果代码为核心代码,执行频率非常高,则输出日志建议增加判断,尤其是低级别的输出<debug、info、warn>。
debug日志太多后可能会影响性能,有一种改进方法是:
但更好的方法是Slf4j提供的最佳实践:
一方面可以减少参数构造的开销,另一方面也不用多写两行代码。
7.有意义的日志
通常情况下在程序日志里记录一些比较有意义的状态数据:程序启动,退出的时间点;程序运行消耗时间;耗时程序的执行进度;重要变量的状态变化。
初次之外,在公共的日志里规避打印程序的调试或者提示信息。
8.【强制】应用中不可直接使用日志系统(Log4j、Logback)中的API,而应依赖使用日志框架
SLF4J中的API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Abc.class);
- 【强制】日志文件推荐至少保存15天,因为有些异常具备以“周”为频次发生的特点。
- 【强制】应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:
appName_logType_logName.log。logType:日志类型,推荐分类有stats/desc/monitor/visit等;logName:日志描述。这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。
说明:推荐对日志进行分类,错误日志和业务日志尽量分开存放,便于开发人员查看,也便于通过日志对系统进行及时监控。
- 【强制】避免重复打印日志,浪费磁盘空间,务必在xml中设置additivity=false
- 【强制】异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么往上抛。
正例:logger.error(各类参数或者对象toString + "_" + e.getMessage(), e);
- 输出的POJO类必须重写toString方法,否则只输出此对象的hashCode值(地址值),没啥参考意义。
- 【推荐】可以使用warn日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。注意日志输出的级别,error级别只记录系统逻辑出错、异常、或者重要的错误信息。如非必要,请不要在此场景打出error级别,避免频繁报警。
- 【推荐】谨慎地记录日志。生产环境禁止输出debug日志;有选择地输出info日志;如果使用warn来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志。解读:深入理解日志级别含义,以及应该如何选择合适的等级。
说明:大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。纪录日志时请思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处?