Java日志打点的最佳实践
副标题#e#
概述
对付此刻的应用措施来说,日志的重要性是不问可知的。很难想象没有任何日志记录成果的应用措施运行在出产情况中。日志所能提供的成果是多种多样的,包罗记录措施运行时发生的错误信息、状态信息、调试信息和执行时间信息等。在出产情况中,日志是查找问题来历的重要依据。应用措施运行时的发生的各类信息,都应该通过日志 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 作为常用。
#p#分页标题#e#
日志记录 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 类的方法来过滤。
#p#副标题#e#
名目化器
实际记录的日志中除了利用记录器工具时提供的动静之外,还包罗一些元数据。这些元数据由日志记录框架来提供。常用的信息包罗记录器的名称、时间戳、线程名等。名目化器用来确定所有这些信息在日志记录中的展示方法。差异的日志记录实现提供各自默认的名目化方法和自界说支持。
JUL 中通过担任 java.util.logging.Formatter 类来自界说名目化的方法,并提供了两个尺度实现 SimpleFormatter 类和 XMLFormatter 类。清单 1 中给出了 JUL 中自界说名目化器的实现方法,只需要担任自 Formatter 类并实现 format 要领即可。参数 LogRecord 类的工具中包括了日志记录中的全部信息。
清单 1. JUL 中自界说名目化器的实现
点击查察代码清单
对付自界说的名目化器类,需要在 JUL 的设置文件中举办指定,如清单 2 所示。
清单 2. 在 JUL 设置文件中指定自界说的名目化器类
java.util.logging.ConsoleHandler.formatter = logging.jul.CustomFormatter
Log4j 在名目化器的实现上要简朴一些,由 org.apache.log4j.PatternLayout 类来认真完成日志记录的名目化。在自界说时不需要建设新的 Java 类,而是通过设置文件指定所需的名目化模式。在名目化模式中,差异的占位符暗示差异范例的信息。好比“%c”暗示记录器的名称,“%d”暗示日期,“%m”暗示日志的动静文本,“%p”暗示严重性级别,“%t”暗示线程的名称。清单 3 给出了 Log4j 设置文件中日志记录的自界说方法。
清单 3. Log4j 中日志记录的自界说方法
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%p] %c – %m%n
日志处理惩罚器
日志记录颠末名目化之后,由差异的处理惩罚器来举办处理惩罚。差异的处理惩罚器有各自差异的处理惩罚方法。好比节制台处理惩罚器会把日志输出到节制台中,文件处理惩罚器把日志写入到文件中。除了这些之外,尚有写入到数据库、通过邮件发送、写入到 JMS 行列等各类差异的处理惩罚方法。
日志处理惩罚器也可以设置所处理惩罚日志信息的最低严重性级别。低于该级此外日志不会被处理惩罚。这样可以节制所处理惩罚的日志记录数量。好比节制台处理惩罚器的级别一般配置为 INFO,而文件处理惩罚器则一般配置为 DEBUG。
日志记录框架一般提供了较量多的日志处理惩罚器实现。开拓人员也可以建设自界说的实现。
Java 日志封装 API
除了 JUL 和 log4j 这样的日志记录库之外,尚有一类库用来封装差异的日志记录库。这样的封装库中一开始以 Apache Commons Logging 框架最为风行,此刻较量风行的是 SLF4J。这样封装库的 API 都较量简朴,只是在日志记录库的 API 基本上做了一层简朴的封装,屏蔽差异实现之间的区别。由于日志记录实现所提供的 API 大抵上较量相似,封装库的浸染更多的是到达语法上的一致性。
#p#分页标题#e#
在 Apache Commons Logging 库中,焦点的 API 是 org.apache.commons.logging.LogFactory 类和 org.apache.commons.logging.Log 接口。LogFactory 类提供了工场要领用来建设 Log 接口的实现工具。好比 LogFactory.getLog 可以按照 Java 类或名称来建设 Log 接口的实现工具。Log 接口中为 6 个差异的严重性级别别离界说了一组要领。好比对 DEBUG 级别,界说了 isDebugEnabled()、debug(Object message) 和 debug(Object message, Throwable t) 三个要领。从这个条理来说,Log 接口简化了对付日志记录器的利用。
SLF4J 库的利用方法与 Apache Commons Logging 库较量雷同。SLF4J 库中焦点的 API 是提供工场要领的 org.slf4j.LoggerFactory 类和记录日志的 org.slf4j.Logger 接口。通过 LoggerFactory 类的 getLogger 要领来获取日志记录器工具。与 Apache Commons Logging 库中的 Log 接口雷同,Logger 接口中的要领也是凭据差异的严重性级别来举办分组的。Logger 接口中有同样 isDebugEnabled 要领。不外 Logger 接口中发出日志记录请求的 debug 等要领利用 String 范例来暗示动静,同时可以利用包括参数的动静,如清单 4 所示。
清单 4. SLF4J 的利用方法
public class Slf4jBasic { private static final Logger LOGGER = LoggerFactory.getLogger(Slf4jBasic.class); public void logBasic() { if (LOGGER.isInfoEnabled()) { LOGGER.info("My log message for %s", "Alex"); } } }
MDC
MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 提供的一种利便在多线程条件下记录日志的成果。某些应用措施回收多线程的方法来处理惩罚多个用户的请求。在一个用户的利用进程中,大概有多个差异的线程来举办处理惩罚。典范的例子是 Web 应用处事器。当用户会见某个页面时,应用处事器大概会建设一个新的线程来处理惩罚该请求,也大概从线程池中复用已有的线程。在一个用户的会话存续期间,大概有多个线程处理惩罚过该用户的请求。这使得较量难以区分差异用户所对应的日志。当需要追踪某个用户在系统中的相关日志记录时,就会变得很贫苦。
一种办理的步伐是回收自界说的日志名目,把用户的信息回收某种方法编码在日志记录中。这种方法的问题在于要求在每个利用日志记录器的类中,都可以会见到用户相关的信息。这样才大概在记录日志时利用。这样的条件凡是是较量难以满意的。MDC 的浸染是办理这个问题。
MDC 可以当作是一个与当前线程绑定的哈希表,可以往个中添加键值对。MDC 中包括的内容可以被同一线程中执行的代码所会见。当前线程的子线程会担任其父线程中的 MDC 的内容。当需要记录日志时,只需要从 MDC 中获取所需的信息即可。MDC 的内容则由措施在适当的时候生存进去。对付一个 Web 应用来说,凡是是在请求被处理惩罚的最开始生存这些数据。清单 5 中给出了 MDC 的利用示例。
清单 5. MDC 利用示例
public class MdcSample { private static final Logger LOGGER = Logger.getLogger("mdc"); public void log() { MDC.put("username", "Alex"); if (LOGGER.isInfoEnabled()) { LOGGER.info("This is a message."); } } }
清单 5 中,在记录日志前,首先在 MDC 中生存了名称为“username”的数据。个中包括的数据可以在名目化日志记录时直接引用,如清单 6 所示,“%X{username}”暗示引用 MDC 中“username”的值。
清单 6. 利用 MDC 中记录的数据
log4j.appender.stdout.layout.ConversionPattern=%X{username} %d{yyyy-MM-dd HH:mm:ss} [%p] %c – %m%n
日志记录最佳实践
下面主要先容一些在记录日志时的较量好的实践。
查抄日志是否可以被记录
当日志记录器收到一个日志记录请求时,假如请求的严重性级别低于记录器工具的实际有效级别,则该请求会被忽略。在日志记录要领的实现中会首先举办这样的查抄。不外推荐的做法是在挪用 API 举办记录之前,首先举办相应的查抄,这样可以制止不须要的机能问题,如清单 7 所示。
清单 7. 查抄日志是否可以被记录
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("This is a message.");
}
#p#分页标题#e#
清单 7 中的做法的浸染在于制止告终构日志记录动静所带来的开销。日志动静中凡是包括与当前上下文相关的信息。为了获取这些信息并结构相应的动静文本,不行制止会发生特另外开销。尤其对付 DEBUG 和 TRACE 级此外日志动静来说,它们所呈现的频率很高,累加起来的开销较量大。因此在记录 INFO、DEBUG 和 TRACE 级此外日志时,首先举办相应的查抄是一个好的实践。而 WARN 及其以上级此外日志则一般不需要举办查抄。
日志中包括充实的信息
日志中所包括的信息应该是充实的。在记录日志动静时应该尽大概多的包括当前上下文中的各类信息,以利便在碰着问题时可以快速的获取到所需的信息。好比在网上付出成果中,与付出相关的日志应该完整的包括当前用户、订单以及付出方法等全部信息。一种较量常见的做法是把相关的日志记录分手在由差异日志记录器所记录的日志中。当呈现问题之后,需要手工查找并匹配相关的日志来定位问题,所耗费的时间和精神会更多。因此,应该尽大概在一条日志记录中包括足够多的信息。
利用符合的日志记录器名称
一般的日志记录实践是利用当前 Java 类的全名作为其利用的日志记录器的名称。这样做可以获得一个与 Java 类和包的条理布局相对应的日志记录器的条理布局。可以很利便的凭据差异的模块来配置相应的日志记录级别。不外对付某些全局的或是横切的成果,如安详和机能等,则推荐利用成果相关的名称。好比措施中大概包括用来提供机能分解信息的日志记录。对付这样的日志记录,应该利用同一名称的日志记录器,如雷同“performance”或“performance.web”。这样当需要启用和禁用机能分解时,只需要设置这些名称的记录器即可。
利用半布局化的日志动静
在先容日志记录 API 中的名目化器时提到过,日志记录中除了根基的日志动静之外,还包罗由日志框架提供的其他元数据。这些数据凭据给定的名目呈此刻日志记录中。这些半布局化的名目使得可以通过东西提取日志记录中的相关信息举办阐明。在利用日志 API 举办记录时,对付日志动静自己,也推荐利用半布局化的方法来组织。
好比一个电子商务的网站,当用户登录之后,该用户所发生的差异操纵所对应的日志记录中都可以包括该用户的用户名,并以牢靠的名目呈此刻日志记录中,如清单 8 所示。
清单 8. 利用半布局化的日志动静
[user1] 用户登录乐成。
[user1] 用户乐成购置产物 A。
[user2] 订单 003 付款失败。
当需要通过日志记录来排查某个用户所碰着的问题时,只需要通过正则表达就可以很快地查询到用户相关的日志记录。
日志聚合与阐明
在措施中正确的处所输出符合的日志动静,只是公道利用日志的第一步。日志记录的真正浸染在于当有问题产生时,可以或许辅佐开拓人员很快的定位问题地址。不外一个实用的系统凡是由许多个差异的部门构成。这个中包罗所开拓的措施自己,也包罗所依赖的第三方应用措施。以一个典范的电子商务网站为例,除了措施自己,还包罗所依赖的底层操纵系统、应用处事器、数据库、HTTP 处事器和署理处事器缓和存等。当一个问题产生时,真正的原因大概来自措施自己,也大概来自所依赖的第三方措施。这就意味着开拓人员大概需要查抄差异处事器上差异应用措施的日志来确定真正的原因。
日志聚合的浸染就在于可以把来自差异处事器上差异应用措施发生的日志聚合起来,存放在单一的处事器上,利便举办搜索和阐明。在日志聚合方面,已经有不少成熟的开源软件可以很好的满意需求。本文中要先容的是 logstash,一个风行的事件和日志打点开源软件。logstash 回收了一种简朴的处理惩罚模式:输入 -> 过滤器 -> 输出。logstash 可以作为署理措施安装到每台需要收集日志的呆板上。logstash 提供了很是多的插件来处理惩罚差异范例的数据输入。典范的包罗节制台、文件和 syslog 等;对付输入的数据,可以利用过滤器来举办处理惩罚。典范的处理惩罚方法是把日志动静转换成布局化的字段;过滤之后的功效可以被输出到差异的目标地,好比 ElasticSearch、文件、电子邮件和数据库等。
Logstash 在利用起来很简朴。从官方网站下载 jar 包并运行即可。在运行时需要指定一个设置文件。设置文件中界说了输入、过滤器和输出的相关设置。清单 9 给出了一个简朴的 logstash 设置文件的示例。
查察本栏目
清单 9. logstash 设置文件示例
input { file { path => [ "/var/log/*.log", "/var/log/messages", "/var/log/syslog" ] type => 'syslog' } } output { stdout { debug => true debug_format => "json" } }
#p#分页标题#e#
清单 9 中界说了 logstash 收集日志时的输入(input)和输出(output)的相关设置。输入范例是文件(file)。每种范例输入都有相应的设置。对付文件来说,需要设置的是文件的路径。对每种范例的输入,都需要指定一个范例(type)。该范例用来区分来自差异输入的记录。代码中利用的输出是节制台。设置文件完成之后,通过“java -jar logstash-1.1.13-flatjar.jar agent -f logstash-simple.conf”就可以启动 logstash。
在日志阐明中,较量重要的是布局化的信息。而日志信息凡是只是一段文本,个中的差异字段暗示差异的寄义。差异的应用措施发生的日志的名目并不沟通。在阐明时需要存眷的是个中包括的差异字段。好比 Apache 处事器会发生与用户会见请求相关的日志。在日志中包括了会见者的各类信息,包罗 IP 地点、时间、HTTP 状态码、响应内容的长度和 User Agent 字符串等信息。在 logstash 收集到日志信息之后,可以按照必然的法则把日志信息中包括的数据提取出来并定名。logstash 提供了 grok 插件可以完成这样的成果。grok 基于正则表达式来事情,同时提供了很是多的常用范例数据的提取模式,如清单 10 所示。
清单 10. 利用 grok 提取日志记录中的内容
点击查察代码清单
在颠末上面 grok 插件的提取之后,Apache 会见日志被转换成包括字段 client、method、request、status、bytes 和 useragent 的名目化数据。可以按照这些字段来举办搜索。这对付阐明问题和举办统计都是很有辅佐的。
当日志记录通过 logstash 举办收集和处理惩罚之后,凡是会把这些日志记录生存到数据库中举办阐明和处理惩罚。今朝较量风行的方法是生存到 ElasticSearch 中,从而可以操作 ElasticSearch 提供的索引和搜索本领来阐嫡志。已经有不少的开源软件在 ElasticSearch 基本之上开拓出相应的日志打点成果,可以很利便的举办搜索和阐明。本文中先容的是 Graylog2。
Graylog2 由处事器和 Web 界面两部门构成。处事器认真吸收日志记录并生存到 ElasticSearch 之中。Web 界面则可以查察和搜索日志,并提供其他的帮助成果。logstash 提供了插件 gelf,可以把 logstash 收集和处理惩罚过的日志记录发送到 Graylog2 的处事器。这样就可以操作 Graylog2 的 Web 界面来举办查询和阐明。只需要把清单 9 中的 logstash 的设置文件中的 output 部门改成清单 11 中所示即可。
清单 11. 设置 logstash 输出到 Graylog2
output { gelf { host => '127.0.0.1' } }
在安装 Graylog2 时需要留意,必然要安装与 Graylog2 的版内情对应的版本的 ElasticSearch,不然会呈现日志记录无法生存到 ElasticSearch 的问题。本文中利用的是 Graylog2 处事器 0.11.0 版本和 ElasticSearch 0.20.4 版本。
除了 Graylog2 之外,别的一个开源软件 Kibana 也较量风行。Kibana 可以当作是 logstash 和 ElasticSearch 的 Web 界面。Kibana 提供了越发富厚的成果来显示和阐嫡志记录。与代码清单中的 logstash 的设置相似,只需要把输出改为 elasticsearch 就可以了。Kibana 可以自动读取 ElasticSearch 中包括的日志记录并显示。
小结
日志记录是应用措施开拓中的重要一环。不外这一环较量容易被开拓人员忽视,因为它所发生的影响在措施运行和维护时。对付一个出产系统来说,日志记录的重要性是不问可知的。本文首先以 java.util.logging 包和 log4j 为例先容了 Java 日志 API 的主要构成部门和利用方法,同时也先容了 Apache Commons Logging 和 SLF4J 两种日志封装 API。本文也给出了一些记录日志时应该回收的最佳实践。最后先容了如何利用开源东西对日志举办聚合和阐明。通过本文,开拓人员可以相识如安在开拓中有效的利用日志。