高性能的智能日志

【编者按】本文做者是 Archanaa Panda ,从 2000 以来一直在软件开发(构架、设计和编程)团队担任 Java / JavaEE 构架师,目前立志于作一个与时俱进的独立的顾问架构师。在本篇文章中,做者经过多个方面为生产环节的日志提供建议和指导,最后还介绍了一个高性能的智能日志技术,帮助你们构建高性能的智能日志框架。
  html

1.摘要

当应用在生产过程当中,日志一般处于开发周期的次要位置,但实际上高性能的日志可能成为开发团队的重要生命线。在此咱们假设读者已熟悉了各类日志框架,如 Log4j 、 SLF4J 等,因此再也不详细介绍,本文旨在为「真实」的生产日志提供指南,检测其对应用质量的影响,同时还为你们介绍了一个被遗忘已久的高性能的智能日志技术。java

2.介绍

在为应用搭建架构、设计、开发甚至是提高性能的整个环节中,你们都经常忽略日志的重要性。最后当应用程序一切准备就绪打算部署的时候,会发生什么呢?数据库

糟糕!应用程序已经脱离你的开发环境,它没法运行出你想要的 IDE 平台,并且没有对应的调试器可用,此时你才想起来日志的重要性彷佛为时已晚。而后当你费劲地想从一大堆日志中尝试调试出应用哪里出了问题,才发现这个任务不只仅是艰巨,并且极其无聊、繁琐,还会浪费大量的时间。根据你多年的经验和专业知识来看,这样的作法毫无疑问是大海捞针!再或者你把这个任务分配给下属或运维团队,而这样一份繁琐无比费力不讨好的工做,搁谁身上都会招致抱怨!甚至他们也无功而返,集体抱怨你的失策,你只有让专业架构师和设计师参与进来。apache

因此,日志应该是生产应用的重要生命线,谁都不该该掉以轻心。固然,众所周知,能够根据需求开启或关闭各类日志的记录级别,市面上知道有多种日志类别和日志框架,如 Log4j 、 Commons Logging 或是 SLF4J ,咱们能够直接将日志发到不一样目的地,如文件、数据库、 JMS 队列等。编程

可是咱们中有多少人会真正地计划日志呢?又有多少人理解日志是如何影响系统质量的呢?谁又会不断地去优化日志,时刻记得一旦应用上线,日志会对系统和工做生活产生哪些影响?还有多少人已经尝试过利用日志框架的先进功能来提高日志性能呢?后端

本文主要想唤醒你们对应用日志的重视,同时为部署应用日志提供基本指导。最后,本文会介绍一个被遗忘已久的日志技术,在不影响应用性能和质量的前提下,帮助你们实现高质量的日志记录。数组

3.记录其余系统质量属性

3.1.1监控

日志记录是最多见的质量监控方式,它能够帮助应用开发者探究到问题的根源。但这些每每只经过监控来实现,对于开发者来讲,在代码的各个地方编写一个 System.out.println() 或 logger.log() 语句再简单不过。然而,问题在于大量的日志可能会致使有用的事件日志被忽略掉,这样彻底达不到日志记录的目的。因此,开发者可能会寻找其余系统监控技术,如使用或开发 JMX 控制台。这一点会在后面的 「为待生产的应用进行日志记录」章节中详细讨论。安全

3.1.2 性能

无论咨询哪位性能专家或架构师,对于 90% 的应用来讲,过多的日志记录都会对性能产生很是不利的影响。日志是一种 I/O 密集型活动,的确会对应用性能产生不良影响,特别是传统的日志方式还会写入应用线程环境的 FileAppender 中。不只如此,日志代码还会形成大量的堆消耗和垃圾收集,好比:服务器

 if (logger.isDebugEnabled())
    
     logger.debug("name "+person.name+" age "+person.age+" address "+person.address);

除此以外,内部调用日志到 Log4J Appender 的 doappend() 方法,会与线程安全同步。这也意味着,应用线程不只在同步地进行大量的磁盘 I/O 操做,还会在写入日志时互相阻塞!在某电子政务门户网站上最严峻的性能情形之一就是,线程转储显示在日志记录写入到单个文件 appender 时,应用日志也被写入到这个的 appender 文件集中!网络

事实上,性能专家首先会肯定应用的当前日志级别,而后一般只是把日志级别从 DEBUG 切换 INFO 或 WARNING 模式,来达到提高应用性能的目的。可是,在完成性能基准测试工做或即时可伸缩问题以后,应用开发者在寻找应用功能性 bug 的根本缘由时,又会将日志级别改回到 DEBUG 模式。事实上,这并非一个科学的日志操做。在「为待生产的应用进行日志记录」中,咱们还会进一步讨论日志规范和卫生维护。「高性能质量的日志记录」中也会详细阐述在不影响应用性能的前提下可以实现质量记录的相关技术。

3.1.3安全性(审核和其余敏感信息)

审核日志是一类特殊的日志,主要用于应用的安全审核和跟踪用户操做。如下示例主要介绍了日志在安全性方面的帮助。

可是,若是走向另外一个极端,日志中携带的敏感信息,如用户的账户密码,可能会暴露系统漏洞。

第三,记录应用程序的事件和流程可能有助于开发者监控和调试,但同时可能会无心地暴露应用的内部架构。

在当前的云应用环境中,应用能够在公共云上托管,这样的漏洞会对应用全部者的知识产权构成威胁。

3.1.4可扩展性和高可用性

日志记录会和扩展性、高可用性之间互相影响。利用「高性能质量的日志」技术来提升应用性能,用更少的硬件提升扩展性和可用性,在现有的资源和条件下,你的应用彻底能够「重拳出击」。

当应用被扩展成可以对可用性进行主动或被动配置时,就会有多个应用实例,而日志策略就显得很是重要。应用是否能支持,或者开发团队是愿意收集来自10个不一样机器或目录的日志,仍是找一个位置能集中收集日志呢?此时,集中的分布式日志变得相当重要。

3.1.5可恢复性

像如 Oracle 这样的重要数据库,已经使用 Redo 日志来确保事务恢复。应用也能够参考这种作法,使用一类特殊的日志帮助恢复,以防万一。

3.1.6错误处理和容错

在大多数应用中,日志只是其中一种错误处理方式,有时只用来评估错误。在复发性错误,如短信/邮件服务器或数据库长期不可用的状况下,重复地、频繁地记录错误是百害而无一利,特别在大量的异常堆栈跟踪下,只会大大地增长 I/O 活动。在这个过程当中,当你要分析一个星期前被忽略的老问题时,这时候关于这个问题的日志早已滚动更新,这种方法只会「火上浇油」。

3.1.7容量

在考虑应用的容量问题时,架构师会参考生产环节的日志大小,再估计所需的磁盘空间、集中文件系统的配置等。

对于分布式环境中的集中式日志,也须要估计分布在网络到远程机器的日志对象的大小。

4.缺乏重要日志的案例分析

本节主要介绍做者对嵌入式应用案例的研究经验,若是在架构、设计和开发阶段忽略了日志记录的重要性,问题一旦发生,以前所作的一切可能功亏一篑,只得在惨痛的教训面前学着「吃一堑,长一智」。

举一个你们都熟悉的场景, GPS 设备就是一个嵌入式应用,装载在车中能够进行位置跟踪。该设备不提供任何用户界面,除了 LEDs 和几个按钮,因此几乎没有人来管理车辆内部的应用程序,不像豪华车型那样会和服务器端应用进行交互。所以,若是设备应用出现了问题,应用开发者应该如何诊断问题根源呢?随着各类卡车被运往全国各地,日志又是什么时候写入车内设备的呢?

应用开发者可能会异想天开,全国各地都配备了服务工程师,可以取下设备带回去进一步分析。以上设想纯属虚构,实际上,开发者只会和服务工程师登入设备的操做系统去复制日志。但面对繁杂的日志,连续的加班做战只会让人身心疲惫!

为了更方便地完成这项工做,应用开发者在设备的桌面服务应用中加上「日志下载」按钮。服务工程师就能够直接利用笔记本里的服务应用下载相关的设备日志。这至少是一个进步,至少没必要再让车停下来再取走设备了,也给了本该陪同服务工程师夜以继日下载日志的可怜开发者们一丝喘息的空间。显然,服务工程师也不至于全世界乱跑了,他们只要盯着应用开发团队注意下载日志便可。

最后,开发团队不得不提升设备应用的性能,让它像发送其余跟踪数据同样,直接经过无线 GPRS 就能够发送日志到后端服务器。

须要注意是,在初始的预估和开发过程当中,全部这些额外工做都没有被计入需求池或预算中。开发团队已经有一个典型的由客户功能需求驱动的思惟定势。应用日志既不是一个客户驱动的需求,也并不是是突出的非功能属性。因此新手开发者一般缺少远见,也无力说服高层或管理者给他们足够的时间预算来创建这样的设施。当这些设备准备上市时,他们遇到了这样的问题,经理和客户气得脸红脖子粗,而他们不得不挑灯夜战。真是一个费力不讨好的活儿!

5. 为待生产的应用进行日志记录

and don'ts in this section, which would make an application production ready.
在上一节「记录其余系统质量属性」中,咱们已经遇到了一些在生产环境中不得不面临的问题。接下来在本节中,咱们再罗列出哪些该作和哪些不应作,为应用的待生产状态作准备。

日志规范和代码审查

日志规范极其重要,由于这一步将为本文讨论的其余最佳实践铺平道路。事实上,许多生产系统还会有那些无聊的日志,如 「 Hi 」 、 「 Came over here 」、「 Done 」、「 xxxyyyyzzzz 」。这些日志一般会在应用调试阶段或开发阶段的单元测试中产生。

但生产阶段还有人仍然采用这样的无聊日志,其给出的理由是它们只会在调试时产生,并且便于关闭。可是,在实践中开发者不多这样作,关闭的同时也关掉了一些有价值的日志。为了更好地控制日志,须要开发者很是精细地配置日志框架,但在生产中却经常忽略这一点

同时,代码审查必须提升效率。当高级开发者或团队领导审查日志时,必须确保删除掉无用的日志,即便要面对一些挑衅的言论,好比有人说「我已经在测试时删过日志了」,「不就是个日志么!它又不是引起问题的缘由」。但这是一个很好的规则,日志并非开发过程当中用来调试问题的方式,负责调试的是咱们 IDE 的调试器!

当你对应用进行模块化时,也须要关注集中式日志

应用日志写入应用服务器的 SystemOut 或 SystemError 文件显示不是最高效的办法,但在生产环境中仍然常见,或如前所述,电子政务门户的线程互相阻塞,共同争夺一个 FileAppender 或者一个文件 I/O 。

最起码,开发者应该将有基于软件包或彻底限定类为已有的记录器命名约定,并可能将不一样的日志分类记录到不一样的 Appender 位置。

在生产过程当中,应该牢记日志级别应该是 WARNING 或根据日志信息设为 INFO 级别。

一个有效的方法是在中央配置或常量类中,列举出全部可能的日志,并只容许开发者使用这些日志。咱们将在「设计高性能的智能日志」章节中进行讨论。

在集群环境和分布式环境中记录日志

几乎全部的服务器端应用都必须采用集群和分布式环境,由于这两种技术能够提供可扩展性和可用性。在集群环境中,日志应该反映出组件、模块、子系统以及其过程实例。

在分布式和集群的环境采用集中的日志服务器,能够避免从多个目录和机器上收集日志的繁琐。同时,对于移动 I/O 到一个单独的机器上也更加方便,而应用性能能够再也不受日志 I/O 的影响。

在厚分布式客户端或嵌入式应用中进行日志记录

在「缺乏重要日志的案例分析」章节中的趣闻中也提道,在厚客户端或嵌入式应用的日志几乎不会发送给到开发团队,也无从帮助他们进行问题分析。远程传输日志的机制须要再深思熟虑,再适当地列入项目计划进行开发。

使用映射诊断环境和嵌套诊断环境

Log4j 的映射诊断环境( MDC )和嵌套诊断环境( NDC )使用 ThreadLocal 储存环境特定信息。它们能够存储如用户名、事务 ID 这样的信息,来识别特定用户或事务所作的所有操做。这就不须要为了日志记录,在类和方法中传递特定环境信息。利用 PatternLayout 的 %X 或 X { key } ,存储的值将在日志中呈现。

规划日志的生命周期和维护

这包括规划日志滚动更新以前的日志文件大小和最大数量。为何须要规划呢?由于日志文件经常大到用文本编辑器都打不开!正如脚本会按期对数据库进行备份同样,也应该有脚原本备份和归档日志。当超出磁盘空间限制时,压缩日志文件也是不错的想法,这样远程传输起来会更加容易。

抵制实时记录源位置信息的诱惑

获取位置信息经常以昂贵的性能损失为代价,由于日志框架试图肯定当前的线程堆栈,从而得到该方法、文件名和行数。确切地说,日志信息自己就能够经过提供服务器、子系统、模块、组件、线程等信息找出日志的来源。

避免重复使用长堆栈跟踪来记录错误

若是可能的话,日志中应该有足够的信息显示错误发生的位置,并尽量避免巨大的堆栈跟踪。固然,这不是一个像 NullPointerException 那样的特例。但它能够为一些容易识别的特定应用错误进行记录。此外,当常常性问题长期发生时,如与 Email 、短信或数据库服务器的链接问题,日志记录也会每隔5分钟地记录该问题,而不是每隔几秒就用巨大的堆栈跟踪填充日志。

不要盲目使用 AOP 注入记录,尤为在生产过程当中

对于新手来讲,最基本的 AOP 教材案例就是日志。由于日志自己就是一个横切关注点,新手能够在进入方法以前或退出方法以后注入日志。在应用于生产有价值的应用以前,应该严肃地考虑这个示例或观点。对于以上已经创建的示例,日志记录可不是一件小事,它值得像大多数其余非功能性需求( NFRs )同样进行详细布局规划。

别把日志看成其余监控手段的替代品

最滑稽地使用日志记录的典型案例之一就是「性能日志」,以下所示:

19 Sept 2010 10:20:30 PERF INFO Thread-25 OrderInsertAction.java Time taken in processing OrderInsertAction: 50ms

19 Sept 2010 10:20:33 PERF INFO Thread-8 OrderInsertDao.java Time taken in insert 30ms

笔者就曾犯过这样的问题,却没意识到它增长 I/O 对性能产生的严重危害。

更明智的作法是捕获 「 TimeStatistic 」 的总时间,并用计数器算出用 GUI 屏幕显示一样内容的平均时间、最长时间、最短期。

6.设计高性能的智能日志

在这一节中主要讨论的策略是将集中式日志包装成记录器,日志采用整数编码而不是字符串。这项技术已经由做者在导师的建议下成功地实现。

目前在网上有许多文章都介绍如何用 JMS 队列和主题或 sockets 来构建集中式日志记录。集中式日志记录可以经过移动 I/O 活动到不一样的机器上进一步提升性能,虽然会对程序节点有轻微的开销。

可是,这里的关键是结合代码来记录集中式日志,而非冗长的字符串。现有框架 Log4J 或者 Commons Logging 鼓励使用字符串来记录信息,这样的做法会对内存、磁盘和网络资源形成必定影响,而这些工做彻底能够经过一段简单的代码搞定。

一个单独的文件能够列出错误代码和可识别字符串之间的映射。

如如下日志记录:

[090822 16:02:48] TX WARNING (Tx-2-thread-1: 1163 transmitData): Server has not responded with an ACK, so trying again.

与下面的日志进行对比

1300604499194,4,192168001002,20600,1001,2,500000

以上日志显示了长时间戳、日志级别、生成日志的机器IP地址、处理的整数值、处理模块、应用的实例ID和一个完整的错误代码,代码翻译过来也会传达相同的含义。这种对象很是便于在网络中以二进制格式进行传输。若是有上下文信息能进一步限定日志中的信息,一个 Object[] 数组也能够被传递,而主错误代码将转化成为带有 printf() 格式占位符的字符串。

使用短码表示错误的作法,几乎在全部的主流产品中都很常见,如 Oracle 、 WebSphere 、 Microsoft 。例如,在微软的 Office 应用出现错误时,所反馈的错误对话框就是一个难以读懂的整数代码,而后会发送给微软用于诊断。

在查看错误时,能够将各类错误代码翻译成完整的字符串进一步解读。

这样作的好处有以下几点:

  • 能节省磁盘空间,从而延长日志的保质期和缩减文件大小

  • 应用内部设计和执行的一些安全措施不会被日志暴露。但在脱机查看时,日志能够经过使用翻译机来翻译全文。

  • 避免构造或传输长字符串,进一步减小内存使用。

  • 网络传输中日志是很是轻量的,因此在调试日志时也尽量保持最小开销。

  • 防止日志随机构造

  • 经过自定制工具更高效地搜索特定错误

此外,经过防止直接使用 Log4J 或其余相似的框架能够执行日志记录,或者在你最喜欢的日志框架上或自定制日志上编写一个定制格式,好比:

public class LogClientFacade {

      public void log(int logLevel, int instanceId, int subsystemId, int componentId, int errorCode);

      public void logWithContext(int logLevel, int instanceId, int subsystemId, int componentId, int errorCode, Object[] contextInfo);

      public void logWithEx(int logLevel, int instanceId, int subsystemId, int componentId, int errorCode, Throwable ex);

...

}

这样的日志接口能确保开发者注意到在分布式环境下诊断日志的基本信息,好比子系统、部件或其余,没必要在在实时操做中记录源代码的行号和文件名。

上图显示了一种解决方案的建议设计。其目的是经过在队列和异步处理中收集信息,或在接收器线程中进行转移,尽可能确保日志的处理过程不存在阻塞。这种方式在网络传输过程当中,以二进制序列化格式进行信息传输具备诸多优点,特别是完整的解决方案是同步地使用同一语言时。

当他们倒进服务器查看离线日志时,应该有一个简单的图形用户界面来观看日志。而后用一个翻译机将日志转换为文本格式,而日志自己也会以二进制格式写入磁盘。须要注意的是,这种方法能和云环境很好地关联,从而确保部署的知识产权的保密性。

6.1A 注意实施

扩展示有框架也是一种好方法,如 Log4J 、 Commons Logging 、 SLF4J 。可是,这样作的话,为了遵循框架内部API的通用性,可能会牺牲一部分效率。例如, Log4J 会序列化日志消息,而堆栈跟踪会做为字符串在 SocketAppender 和 JMSAppender 中进行网络传递。该框架至关易于扩展,并且能覆盖所选择框架的特定部分,如经过添加或扩展新的 Appenders ,扩展内部的数据传输对象,如 LoggingEvent ,并进行自定义序列化。再者,若是须要最大的灵活性,你能够仅用较短的时间来建立一个简单的自定义日志框架。

另外一个有趣的决定是在应用运行在服务器时是否使用 JMS ,或者经过使用一个独立队列,如 WebSphereMQ 、HornetQ 或 ActiveMQ 。若是选择 JMS ,如下是做者的几点建议:

  • 使用宽松质量属性来避免增长事务、持续性并容许队列重复。记住,严格可靠性会下降性能,对日志而言这是没必要要的。

  • 在一个 JVM 中,要么是在日志服务器或在客户子系统,最好是使用轻量的 java.util.concurrent 队列或 in-VM 队列实现,从而避免系列化开销。

  • 建议使用消息代理或运输桥梁,而不是用一个集中式队列,并作相同的远程调用。

本人的我的偏好是使用简单的 socket。

7.总结

在这篇文章中,咱们已经讨论了最佳实践和日志记录中所发现的弊端。咱们还提出了一种结合集中式日志和代码的技术,从而取代字符串实现高性能的智能日志实践。

做者将构思高性能智能日志的设计归功于她的导师 Akash Gupt 先生,是 InterGlobe 科技公司的解决方案架构师( http://in.linkedin.com/pub/akash-gupta/3/79/2b3 ),在他的指导下,做者成功地实施并观察到这种技术的巨大性能优点。

8References

8.引用

Pro Apache Log4J by Samudra Gupta

Log4J Source code

(编译自:https://dzone.com/articles/high-performance-and-smarter)

OneAPM 为您提供端到端的 Java 应用性能解决方案,咱们支持全部常见的 Java 框架及应用服务器,助您快速发现系统瓶颈,定位异常根本缘由。分钟级部署,即刻体验,Java 监控历来没有如此简单。想阅读更多技术文章,请访问 OneAPM 官方技术博客

本文转自 OneAPM 官方博客

相关文章
相关标签/搜索