在Java 领域存在大量的日志组件openopen收录了个日志组件日志系统作为一种应用程序服务对于跟蹤调试程序状态记录崩溃数据恢复都有着重要的作用我们可以把Java日志系统看作是必不可少的跟蹤调试工具
简介
日志系统是一种不可或缺的跟蹤调试工具特别是在任何无人职守的后台程序以及那些没有跟蹤调试环境的系统中有着广泛的应用长期以来日志系统作为一种应用程序服务对于跟蹤调试程序状态记录崩溃数据恢复都有非常现实的意义这种服务通常以两种方式存在
日志系统作为服务进程存在Windows中的的事件日志服务就属于这种类型该类型的日志系统通常通过消息队列机制将所需要记录的日志由日志发送端发送给日志服务日志发送端和日志保存端通常不在同一进程当中日志的发送是异步过程这种日志服务通常用于管理员监控各种系统服务的状态
日志系统作为系统调用存在Java世界中的日志系统和Unix环境下诸多守护进程所使用的日志系统都属于这种类型日志系统的代码作为系统调用被编译进日志发送端日志系统的运行和业务代码的运行在同一进程空间日志的发送多数属于同步过程这种日志服务由于能够同步反映处系统运行状态通常用于调试跟蹤和崩溃恢复
本文建立的日志系统基本属于第二种类型但又有所不同该日志系统将利用Java线程技术实现一个既能够反映统一线程空间中程序运行状态的同步日志发送过程又能够提供快速的日志记录服务还能够提供灵活的日志格式配置和过滤机制
系统调试的误区
在控制台环境上调试Java程序时此时往控制台或者文本文件输出一段文字是查看程序运行状态最简单的做法但这种方式并不能解决全部的问题有时候对于一个我们无法实时查看系统输出的系统或者一个确实需要保留我们输出信息的系统良好的日志系统显得相当必要因此不能随意的输出各种不规范的调试信息这些随意输出的信息是不可控的难以清除可能为后台监控错误排除和错误恢复带来相当大的阻力
日志系统框架的基本功能
一个完备的日志系统框架通常应当包括如下基本特性
所输出的日志拥有自己的分类这样在调试时便于针对不同系统的不同模块进行查询从而快速定位到发生日志事件的代码
日志按照某种标准分成不同级别分级以后的日志可以用于同一分类下的日志筛选
支持多线程日志系统通常会在多线程环境中使用特别是在Java系统当中因此作为一种系统资源日志系统应当保证是线程安全的
支持不同的记录媒介不同的工程项目往往对日志系统的记录媒介要求不同因此日志系统必须提供必要的开发接口以保证能够比较容易的更换记录介质
高性能日志系统通常要提供高速的日志记录功能以应对大系统下大请求流量下系统的正常运转
稳定性日志系统必须是保持高度的稳定性不能因为日志系统内部错误导致主要业务代码的崩溃
常用日志系统简介
在Java世界中以下三种日志框架比较优秀
)LogJ
最早的Java日志框架之一由Apache基金会发起提供灵活而强大的日志记录机制但是其复杂的配置过程和内部概念往往令使用者望而却步
)JDKLoggingFramework
继LogJ之后JDK标准委员会将LogJ的基本思想吸收到JDK当中在JDK中发布了第一个日志框架接口并提供了一个简单实现
)CommonsLoggingFramwork
该框架同样是Apache基金会项目其出现主要是为了使得Java项目能够在LogJ和JDKlLoggingFramework的使用上随意进行切换因此该框架提供了统一的调用接口和配置方法
系统设计
由于LogJ得到广泛应用从使用者的角度考虑本文所设计的框架采用了部分LogJ的接口和概念但内部实现则完全不同使用Java实现日志框架关键的技术在于前面提及的日志框架特性的内部实现特别是日志的分类和级别日志分发框架的设计日志记录器的设计以及在设计中的高性能和高稳定性的考虑
系统架构
日志系统框架可以分为日志记录模块和日志输出模块两大部分日志记录模块负责创建和管理日志记录器(Logger)每一个Logger对象负责按照不同的级别(LoggerLevel)接收各种记录了日志信息的日志对象(LogItem)Logger对象首先获取所有需要记录的日志并且同步地将日志分派给日志输出模块日志输出模块则负责日志输出器(Appender)的创建和管理以及日志的输出系统中允许有多个不同的日志输出器日志输出器负责将日志记录到存储介质当中系统结构如下图所示
)thisstylewidth=; border=>
下图使用UML类图给出了日志系统的架构
image onmousewheel=javascript:return big(this) height= alt=日志系统的架构 src=http://imgeducitycn/img_///jpg width= onload=javascript:if(thiswidth>)thisstylewidth=; border=>
在图给出的架构中日志记录器Logger是整个日志系统框架的用户使用接口程序员可以通过该接口记录日志为了实现对日志进行分类系统设计允许存在多个Logger对象每一个Logger负责一类日志的记录Logger类同时实现了对其对象本身的管理LoggerLevel类定义了整个日志系统的级别在客户端创建和发送日志时这些级别会被使用到Logger对象在接收到客户端创建和发送的日志消息时同时将该日志消息包装成日志系统内部所使用的日志对象LogItem日志对象除了发送端所发送的消息以外还会包装诸如发送端类名发送事件发送方法名发送行号等等这些额外的消息对于系统的跟蹤和调试都非常有价值包装好的LogItem最终被发送给输出器由这些输出器负责将日志信息写入最终媒介输出器的类型和个数均不固定所有的输出器通过AppenderManager进行管理通常通过配置文件即可方便扩展出多个输出器
日志记录部分的设计
如前文所述日志记录部分负责接收日志系统客户端发送来的日志消息日志对象的管理等工作下面详细描述了日志记录部分的设计要点
日志记录器的管理
系统通过保持多个Logger对象的方式来进行日志记录的分类每一个Logger对象代表一类日志分类因此Logger对象的名称属性是其唯一标识通过名称属性获取一个Logger对象
LoggerLoggerlogger=LoggergetLogger(LoggerName);
一般的使用类名来作为日志记录器的名称这样做的好处在于能够尽量减少日志记录器命名之间的沖突(因为Java类使用包名)同时能够将日志记录分类得尽可能的精细因此假定有一UserManager类需要使用日志服务则更一般的使用方式为
LoggerLoggerlogger=LoggergetLogger(UserManagerclass);
日志分级的实现
按照日志目的不同将日志的级别由低到高分成五个级别
◆DEBUG表示输出的日志为一个调试信息
◆INFO表示输出的日志是一个系统提示
◆WARN表示输出的日志是一个警告信息
◆ERROR表示输出的日志是一个系统错误
◆FATAL表示输出的日志是一个导致系统崩溃严重错误
这些日志级别定义在LoggerLevel接口中被日志记录器Logger在内部使用而对于日志系统客户端则可使用Logger类接口对直接调用并输出这些级别的日志Logger的这些接口描述如下
publicvoiddebug(Stringmsg);//输出调试信息
publicvoidinfo(Stringmsg);//输出系统提示
publicvoidwarn(Stringmsg);//输出警告信息
publicvoidfatal(Stringmsg);//输出系统错误
publicvoiderror(Stringmsg);//输出严重错误
通过对Logger对象上这些接口的调用直接为日志信息赋予了级别属性这样为后继的按照不同级别进行输出的工作奠定了基础
日志对象信息的获取
日志对象上包含了一条日志所具备的所有信息通常这些信息包括输出日志的时间Java类类成员方法所在行号日志体日志级别等等在JDK中可以通过在方法中抛出并且捕获住一个异常则在捕捉到的异常对象中已经由JVM自动填充好了系统调用的堆栈在JDK中则可以使用javalangStackTraceElement获取到每一个堆栈项的基本信息通过对日志客户端输出日志方法调用层数的推算则可以比较容易的获取到StackTraceElement对象从而获取到输出日志时的Java类类成员方法所在行号等信息在JDK或者更早的版本中相应的工作则必须通过将异常的堆栈信息输出到字符串中并分析该字符串格式得到
日志输出部分的设计
日志输出部分的设计具有一定的难度在本文设计的日志系统中日志的输出多线程的支持日志系统的扩展性日志系统的效率等问题都交由日志输出部分进行管理
日志输出器的继承结构
在日志的输出部分采用了二层结构即定义了一个抽象的日志输出器(AbstractLoggerAppender)然后从该抽象类继承出实际的日志输出器AbstractLoggerAppender定义了一系列的对日志进行过滤的方法而具体输出到存储媒介的方法则是一个抽象方法由子类实现在系统中默认实现了控制台输出器和文件输出器两种其中控制台输出器的实现颇为简单
文件输出器的内部实现
在日志记录部分的实现中并没有考虑多线程高效率等问题因此文件输出器必须考虑这些问题的处理在文件输出器内部使用javalangVector定义了一个线程安全的高速缓沖所有通过日志记录部分分派到文件输出器的日志被直接放置到该高速缓沖当中同时在文件输出器内部定义一个工作线程负责定期将高速缓沖中的内容保存到文件在保存的过程中同时可以进行日志文件的备份等工作由于采用了高速缓沖的结构很显然日志客户端的调用已经不再是一个同步调用从而不再会需要等到文件操作后才返回提高的系统调用的速度该原理如图所示
image onmousewheel=javascript:return big(this) height= alt=文件输出器的内部实现原理 src=http://imgeducitycn/img_///jpg width= onload=javascript:if(thiswidth>)thisstylewidth=; border=>
设计难点
通过上述设计一个具有良好扩展能力的高性能日志系统框架就已经具有了一定的雏形在设计过程中几个难点问题需要进一步反思
一是否整个系统应当采用完全异步的结构通过类似于消息机制的方式来进行由日志客户端发送日志给日志系统这种方式可以作为日志系统框架另一种运行方式在后继设计中加以考虑
二在文件输出器中可以看到目前虽然可以扩展多个日志输出器但是目前提供的抽象类中仅仅提供了对日志的过滤机制而没有提供的缓存机制目前的缓存机制被放在文件输出器中实现因此在未来的进一步设计中可以将文件输出器中的缓存机制上移到抽象类当中
设计模式
在设计过程中我们特别注意使用了数个经典的设计模式如Logger对象的创建使用了工厂方法模式(FactoryMethod)由AbstractLoggerAppender和ConsoleAppender以及FileAppender构成了策略模式(Strategy)除此以外还大量使用了单例模式(Singleton)在设计中适当运用设计模式能够加快设计进度提高设计质量
总结
本文探讨了日志系统的基本特性实现日志系统的意义方法和内部结构并且给出了一种基于Java平台的日志系统的详细设计同时也指出日志系统会向服务化异步化的方向发展作为一种方便的跟蹤调试数据恢复工具应当提倡在适当的环境下对日志系统的使用