分享

Java日志缓存机制的实现(1)

 集微笔记 2013-09-04

概述

日志技术为产品的质量和服务提供了重要的支撑。JDK 在 1.4 版本以后加入了日志机制,为 Java 开发人员提供了便利。但这种日志机制是基于静态日志级别的,也就是在程序运行前就需设定下来要打印的日志级别,这样就会带来一些不便。

在 JDK 提供的日志功能中,日志级别被细化为 9 级,用以区分不同日志的用途,用来记录一个错误,或者记录正常运行的信息,又或是记录详细的调试信息。由于日志级别是静态的,如果日志级别设定过高,低级 别的日志难以打印出来,从而导致在错误发生时候,难以去追踪错误的发生原因,目前常常采用的方式是在错误发生的时候,不得不先调整日志级别到相对低的程 度,然后再去触发错误,使得问题根源得到显现。但是这种发生问题需要改动产品配置,然后重新触发问题进行调试的方式使得产品用户体验变差,而且有些问题会 因为偶发性,环境很复杂等原因很难重新触发。

相反,如果起初就把日志级别调整到比较低,那么日志中间会有大量无用信息,而且当产品比较复杂的时候,会导致产生的日志文件很大,刷新很快,无法及时的记录有效的信息,甚至成为性能瓶颈,从而降低了日志功能对产品的帮助。

本文借助 Java Logging 中的 MemoryHandler 类将所有级别日志缓存起来,在适当时刻输出,来解决这个问题。主要围绕 MemoryHandler 的定义和logging.properties 文件的处理而展开。

实例依附的场景如下,设想用户需要在产品发生严重错误时,查看先前发生的包含 Exception 的错误信息,以此作为诊断问题缘由的依据。使用 Java 缓冲机制作出的一个解决方案是,将所有产品运行过程中产生的包含Exception 的日志条目保存在一个可设定大小的循环缓冲队列中,当严重错误(SEVERE)发生时,将缓冲队列中的日志输出到指定平台,供用户查阅。

Java 日志机制的介绍

Java 日志机制在很多文章中都有介绍,为了便于后面文章部分的理解,在这里再简单介绍一下本文用到的一些关键字。

Level:JDK 中定义了 Off、Severe、Warning、Info、Config、Fine、Finer、Finest、All 九个日志级别,定义 Off 为日志最高等级,All 为最低等级。每条日志必须对应一个级别。级别的定义主要用来对日志的严重程度进行分类,同时可以用于控制日志是否输出。

LogRecord:每一条日志会被记录为一条 LogRecord, 其中存储了类名、方法名、线程 ID、打印的消息等等一些信息。

Logger:日志结构的基本单元。Logger 是以树形结构存储在内存中的,根节点为 root。com.test(如果存在)一定是 com.test.demo(如果存在)的父节点,即前缀匹配的已存在的 logger 一定是这个 logger 的父节点。这种父子关系的定义,可以为用户提供更为自由的控制粒度。因为子节点中如果没有定义处理规则,如级别 handler、formatter 等,那么默认就会使用父节点中的这些处理规则。

Handler:用来处理 LogRecord,默认 Handler 是可以连接成一个链状,依次对 LogRecord 进行处理。

Filter:日志过滤器。在 JDK 中,没有实现。

Formatter:它主要用于定义一个 LogRecord 的输出格式。

图 1. Java 日志处理流程

图 1 展示了一个 LogRecord 的处理流程。一条日志进入处理流程首先是 Logger,其中定义了可通过的 Level,如果 LogRecord 的 Level 高于Logger 的等级,则进入 Filter(如果有)过滤。如果没有定义 Level,则使用父 Logger 的 Level。Handler 中过程类似,其中 Handler 也定义了可通过 Level,然后进行 Filter 过滤,通过如果后面还有其他 Handler,则直接交由后面的 Handler 进行处理,否则会直接绑定到 formatter 上面输出到指定位置。

在实现日志缓存之前,先对 Filter 和 Formatter 两个辅助类进行介绍。

Filter

Filter 是一个接口,主要是对 LogRecord 进行过滤,控制是否对 LogRecord 进行进一步处理,其可以绑定在 Logger 下或 Handler 下。

只要在 boolean isLoggable(LogRecord)方法中加上过滤逻辑就可以实现对 logrecord 进行控制,如果只想对发生了 Exception 的那些 log 记录进行记录,那么可以通过清单 1 来实现,当然首先需要将该 Filter 通过调用 setFilter(Filter)方法或者配置文件方式绑定到对应的 Logger 或 Handler。

清单 1. 一个 Filter 实例的实现

  1. Override 
  2.  public boolean isLoggable(LogRecord record){ 
  3.  if(record.getThrown()!=null){ 
  4.         return true
  5.  }else
  6.          return false;  
  7.  } 
  8.  } 

Formatter

Formatter 主要是对 Handler 在输出 log 记录的格式进行控制,比如输出日期的格式,输出为 HTML 还是 XML 格式,文本参数替换等。Formatter 可以绑定到 Handler 上,Handler 会自动调用 Formatter 的 String format(LogRecord r) 方法对日志记录进行格式化,该方法具有默认的实现,如果想实现自定义格式可以继承 Formater 类并重写该方法,默认情况下例如清单 2 在经过 Formatter 格式化后,会将 {0} 和 {1} 替换成对应的参数。

清单 2. 记录一条 log

  1. logger.log(Level.WARNING,"this log is for test1: {0} and test2:{1}"
  2.     new Object[]{newTest1(), 
  3.     new Test2()}); 

MemoryHandler

MemoryHandler 是 Java Logging 中两大类 Handler 之一,另一类是 StreamHandler,二者直接继承于 Handler,代表了两种不同的设计思路。Java Logging Handler 是一个抽象类,需要根据使用场景创建具体 Handler,实现各自的 publish、flush 以及 close 等方法。

MemoryHandler 使用了典型的“注册 – 通知”的观察者模式。MemoryHandler 先注册到对自己感兴趣的 Logger 中(logger.addHandler(handler)),在这些 Logger 调用发布日志的 API:log()、logp()、logrb() 等,遍历这些 Logger 下绑定的所有 Handlers 时,通知触发自身 publish(LogRecord)方法的调用,将日志写入 buffer,当转储到下一个日志发布平台的条件成立,转储日志并清空 buffer。

这里的 buffer 是 MemoryHandler 自身维护一个可自定义大小的循环缓冲队列,来保存所有运行时触发的 Exception 日志条目。同时在构造函数中要求指定一个 Target Handler,用于承接输出;在满足特定 flush buffer 的条件下,如日志条目等级高于 MemoryHandler 设定的 push level 等级(实例中定义为 SEVERE)等,将日志移交至下一步输出平台。从而形成如下日志转储输出链:

图 2. Log 转储链

在实例中,通过对 MemoryHandler 配置项 .push 的 Level 进行判断,决定是否将日志推向下一个 Handler,通常在 publish() 方法内实现。代码清单如下:

内容导航
 第 1 页:清单1-3  第 2 页:清单4-6
 第 3 页:清单7-10  第 4 页:清单11-15

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多