疯狂java


您现在的位置: 疯狂软件 >> 新闻资讯 >> 正文

Java 日志管理最佳实践


 

   

  日志记录是应用程序运行中必不可少的一部分。具有良好格式和完备信息的日志记录可以在程序出现问题时帮助开发人员迅速地定位错误的根源。对于开 发人员来说,在程序中使用日志API记录日志并不复杂,不过遵循一些最佳实践可以更好的利用日志。本文介绍了在Java程序中记录日志的最佳实践,同时也 介绍了如何使用开源软件对日志进行聚合和分析。

  概述

  对于现在的应用程序来说,日志的重要性是不言而喻的。很难想象没有任何日志记录功能的应用程序运行 在生产环境中。日志所能提供的功能是多种多样的,包括记录程序运行时产生的错误信息、状态信息、调试信息和执行时间信息等。在生产环境中,日志是查找问题 来源的重要依据。应用程序运行时的产生的各种信息,都应该通过日志 API 来进行记录。很多开发人员习惯于使用 System.out.println、System.err.println 以及异常对象的 printStrackTrace 方法来输出相关信息。这些使用方式虽然简便,但是所产生的信息在出现问题时并不能提供有效的帮助。这些使用方式都应该改为使用日志 API。使用日志 API 并没有增加很多复杂度,但是所提供的好处是显著的。

  尽管记录日志是应用开发中并不可少的功能,在 JDK 的最初版本中并不包含日志记录相关的 API 和实现。相关的 API(java.util.logging 包,JUL)和实现,直到 JDK 1.4 才被加入。因此在日志记录这一个领域,社区贡献了很多开源的实现。其中比较流行的包括 log4j 及其后继者 logback。除了真正的日志记录实现之外,还有一类与日志记录相关的封装 API,如 Apache Commons Logging 和 SLF4J。这类库的作用是在日志记录实现的基础上提供一个封装的 API 层次,对日志记录 API 的使用者提供一个统一的接口,使得可以自由切换不同的日志记录实现。比如从 JDK 的默认日志记录实现 JUL 切换到 log4j。这类封装 API 库在框架的实现中比较常用,因为需要考虑到框架使用者的不同需求。在实际的项目开发中则使用得比较少,因为很少有项目会在开发中切换不同的日志记录实现。 本文对于这两类库都会进行具体的介绍。

  记录日志只是有效地利用日志的第一步,更重要的是如何对程序运行时产生的日志进行处理和分析。典型的 情景包括当日志中包含满足特定条件的记录时,触发相应的通知机制,比如邮件或短信通知;还可以在程序运行出现错误时,快速地定位潜在的问题源。这样的处理 和分析的能力对于实际系统的维护尤其重要。当运行系统中包含的组件过多时,日志对于错误的诊断就显得格外重要。

  本文首先介绍关于日志 API 的基本内容。

  Java 日志 API

  从功能上来说,日志 API 本身所需求的功能非常简单,只需要能够记录一段文本即可。API 的使用者在需要进行记录时,根据当前的上下文信息构造出相应的文本信息,调用 API 完成记录。一般来说,日志 API 由下面几个部分组成:

  记录器(Logger):日志 API 的使用者通过记录器来发出日志记录请求,并提供日志的内容。在记录日志时,需要指定日志的严重性级别。

  格式化器(Formatter):对记录器所记录的文本进行格式化,并添加额外的元数据。

  处理器(Handler):把经过格式化之后的日志记录输出到不同的地方。常见的日志输出目标包括控制台、文件和数据库等。

  记录器

  当 程序中需要记录日志时,首先需要获取一个日志记录器对象。一般的日志记录 API 都提供相应的工厂方法来创建记录器对象。每个记录器对象都是有名称的。一般的做法是使用当前的 Java 类的名称或所在包的名称作为记录器对象的名称。记录器的名称通常是具有层次结构的,与 Java 包的层次结构相对应。比如 Java 类“com.myapp.web.IndexController”中使用的日志记录器的名称一般是 “com.myapp.web.IndexController”或“com.myapp.web”。除了使用类名或包名之外,还可以根据日志记录所对应 的功能来进行划分,从而选择不同的名称。比如用“security”作为所有与安全相关的日志记录器的名称。这样的命名方式对于某些横切的功能比较实用。 开发人员一般习惯于使用当前的类名作为日志记录器的名称,这样可以快速在日志记录中定位到产生日志的 Java 类。使用有意义的其他名称在很多情况下也是一个不错的选择。

  在通过日志记录器对象记录日志时,需要指定日志的严重性级别。根据每个记录器对 象的不同配置,低于某个级别的日志消息可能不会被记录下来。该级别是日志 API 的使用者根据日志记录中所包含的信息来自行决定的。不同的日志记录 API 所定义的级别也不尽相同。日志记录封装 API 也会定义自己的级别并映射到底层实现中相对应的实际级别。比如 JDK 标准的日志 API 使用的级别包括 OFF、SEVERE、WARNING、INFO、CONFIG、FINE、FINER、FINEST 和 ALL 等,Log4j 使用的级别则包括 OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE 和 ALL 等。一般情况下,使用得比较多的级别是 FATAL、ERROR、WARN、INFO、DEBUG 和 TRACE 等。这 6 个级别所对应的情况也有所不同:

  FATAL:导致程序提前结束的严重错误。

  ERROR:运行时异常以及预期之外的错误。

  WARN:预期之外的运行时状况,不一定是错误的情况。

  INFO:运行时产生的事件。

  DEBUG:与程序运行时的流程相关的详细信息。

  TRACE:更加具体的详细信息。

  在这 6 个级别中,以 ERROR、WARN、INFO 和 DEBUG 作为常用。

  日 志记录 API 的使用者通过记录器来记录日志消息。日志消息在记录下来之后只能以文本的形式保存。不过有的实现(如 Log4j)允许在记录日志时使用任何 Java 对象。非 String 类型的对象会被转换成 String 类型。由于日志记录通常在出现异常时使用,记录器在记录消息时可以把产生的异常(Throwable 类的对象)也记录下来。

  每个记录器对 象都有一个运行时对应的严重性级别。该级别可以通过配置文件或代码的方式来进行设置。如果没有显式指定严重性级别,则会根据记录器名称的层次结构关系往上 进行查找,直到找到一个设置了严重性级别的名称为止。比如名称为“com.myapp.web.IndexController”的记录器对象,如果没有 显式指定其严重性级别,则会依次查找是否有为名称“com.myapp.web”、“com.myapp”和“com”指定的严重性级别。如果仍然没有找 到,则使用根记录器配置的值。

  通过记录器对象来记录日志时,只是发出一个日志记录请求。该请求是否会完成取决于请求和记录器对象的严重性级 别。记录器使用者产生的低于记录器对象严重性级别的日志消息不会被记录下来。这样的记录请求会被忽略。除了基于严重性级别的过滤方式之外,日志记录框架还 支持其他自定义的过滤方式。比如 JUL 可以通过实现 java.util.logging.Filter 接口的方式来进行过滤。Log4j 可以通过继承 org.apache.log4j.spi.Filter 类的方式来过滤。

  格式化器

  实际记录的日志中除了使用记录器对象时提供的消息之外,还包括一些元数据。这些元数据由日志记录框架来提供。常用的信息包括记录器的名称、时间戳、线程名等。格式化器用来确定所有这些信息在日志记录中的展示方式。不同的日志记录实现提供各自默认的格式化方式和自定义支持。

  JUL 中通过继承 java.util.logging.Formatter 类来自定义格式化的方式,并提供了两个标准实现 SimpleFormatter 类和 XMLFormatter 类。清单 1 中给出了 JUL 中自定义格式化器的实现方式,只需要继承自 Formatter 类并实现 format 方法即可。参数 LogRecord 类的对象中包含了日志记录中的全部信息。

  清单 1. JUL 中自定义格式化器的实现

  public class CustomFormatter extends Formatter {

  public String format(LogRecord record) {

  return String.format("