java日志框架简介

Java Log Frameworks

常见的java日志框架和实现主要有:

  1. slf4j
  2. log4j
  3. logback
  4. apache jakarta commons-logging
  5. java.util.logging
  6. log4j 2

上述几个日志工具的简单说明:

  1. 前面3个日志工具是同一个作者 Ceki Gülcü 开发的。
  2. slf4jSimple Logging Facade for Java的缩写,主要是在slf4j-api中定义了日志接口和工厂方法,它并不具体实现日志操作,平时项目开发中引用的日志相关的代码都应该来自这个包。
  3. logback-classicslf4j的完整实现,目前有取代log4j的趋势,spring-boot项目就在使用这个日志框架。
  4. slf4j-log4j12是连接slf4j-apilog4j的适配器,它实现了slf4j-apiStaticLoggerBinder接口,从而使得在编译时绑定的是slf4j-log4j12getSingleton()方法。
  5. apache的JCL在较早的一些类库中用得较多,后来更多第三方类库采用log4j,比如strutsspringhibernate
  6. java.util.logging是JDK自带的,日志等级较多,较少使用。
  7. log4j 2在多线程模型中性能远高于其他日志框架。

Logger LEVEL

slf4j中定义的常用的日志级别:

  1. ERROR
  2. WARN
  3. INFO
  4. DEBUG

另外还有一个比DEBUG还详细的TRACE,主要是用来跟踪一些非常复杂的算法中,在每一步计算过程中输出详细的结果,用于跟踪分析,并且代码完成后应该将之移除,不要上传到代码库,因此下面就不做说明了。

日志级别使用场景

DEBUG

  • 开发调试使用
  • 信息面向开发工程师

INFO

  • 对象状态变化前后
  • API方法调用前显示传入的参数列表
  • API方法返回的关键信息
  • 定时任务的开始结束信息
  • 关键方法的进入退出点及相应参数
  • 代码块运行时间监控,性能分析
  • 信息面向开发工程师及运维人员

WARN

  • 不影响程序运行的配置问题
  • 可恢复的异常
  • 信息面向开发工程师及运维人员

ERROR

  • 运行时异常
  • 无法处理的异常
  • 记录异常信息和堆栈,不要吞没异常
  • Logger不是异常处理的工具
  • 信息面向开发工程师及运维人员

不同的运行环境使用不同的日志等级

  1. production - INFO
  2. development/stage - DEBUG
  3. test - DEBUG

Should logger object static or not

  1. 建议使用private static final形式,每个类所有对象共用一个日志对象即可,KISS。
  2. non-static被子类继承的优势,只要在父类里声明一个protected的日志对象,子类就可以直接使用,缺点是每个子类对象创建都会生成一个日志对象。
  3. non-staticIOC友好的,因为IOC中的BEAN一般都是单例的。

STATIC

private static final Logger LOGGER = LoggerFactory.getLogger(LoggerExample.class);

INSTANCE

private final Logger logger = LoggerFactory.getLogger(this.getClass());

日志查询RequstID

如果应用是分布式的,或者是用户会访问多个子系统的,当访问者第一次进入系统时,可以为其生成一个统一的RequestID,并使用MDC(Mapped Diagnostic Context,映射调试上下文)写入日志文件中,可以跟踪用户完整的操作过程,对问题分析和数据分析会很有用。

比如使用如下这些信息通过一个可逆算法生成这个RequestID

  1. JSESSIONID
  2. USERID
  3. TIMESTAMP

使用结构化或者半结构化的日志消息

日志格式设计比较好的日志消息,对于查找问题会很有利,也便于使用logstash导入到elasticsearch中进行分析,或者是kibana查看。

日志命名格式以及日志文件名

日志文件一般按日期,应用标识和日志级别三者来命名,也可以再带上主机名。如:web-info-2016-09-18.log

Logger使用注意事项

不要使用字符串拼接方法

log.debug("orderId is " + order.getId() + " and amounts is " + amounts);

使用对象占位符

log.debug("orderId is {} and amounts is {}", order.getId(), amounts);

记录异常信息

try {    ...} catch (Exception e) {    e.printStackTrace(); // BAD    log.error("IO exception", e.getMessage()); // BAD    log.error("IO exception", e); // OK}

不要记录异常并抛出异常

try {    ...} catch (Exception e) {    log.error("IO exception"); // OK    log.error("IO exception", e); // BAD    throw e;}

不要记录集合和复杂对象

log.info("fetch users: {} from db.", users);

程序中不要使用System.out

System.out.println("current login user name is : " + user.getName());

References

  1. Should logger be private static or not
  2. Should Logger members of a class be declared as static
  3. Get Rid of Java Static Loggers
  4. Java日志性能那些事
  5. Java 日志管理最佳实践
  6. java日志规范
  7. 最佳日志实践