一、 需求目标
日志记录组件负责系统运行期间,记录系统运行和用户操作业务日志。考虑到日志组件的复用性,ERP项目大数据量操作和业务操作的复杂性,要求日志组件具有以下特点:
1. 业务日志记录、输出和查询;
2. 所输出的业务日志应该从业务和操作两个维度进行分类,以支持更加灵活的日志分类检索;
3. 支持多线程:业务日志组件会在多线程环境中使用,需要确保线程安全性;
4. 稳定性:业务日志组件必须保持高度的稳定性,不能因为组件内部错误导致业务代码的崩溃;
5. 高性能:业务日志组件需要提供高速的日志记录功能以应对大请求流量下业务系统的正常运转;
二、 设计思路
1. 方便检索的业务需求限制了日志输出介质必须为数据库或是某种支持快速检索的存储媒介。
2. 为了满足稳定性和高性能的需求,可以采用异步消息来处理日志信息的输出,但会带来一系列编码实现的难度和复杂度。
3. 日志记录切入点,为了减少业务编码过程中过度关注日志记录过程,采用拦截器处理大部分的系统运行日志。
4. 日志组件尽可能提供简单的接口,以方便业务方法中调用。同时为了避免业务方法记录日志时每次手工编码获取当前操作人,当前类名,方法名等信息,应设计某种机制自动获取类似信息。
5. 由于EJB服务组件仅提供业务组件服务,不直接接触用户操作,故无法收集用户的操作信息,用户操作日志的收集交由WEB服务端采集。
6. 考虑到日志检索和效率和日志的膨胀速度,有必要存在一个的日志清理机制。
三、 设计方案
1. 总体功能部署
l WEB端与EJB服务器端各自存在日志生成器,负责收集发生在各自服务范围内的日志信息。
l WEB端和EBJ端的日志生成器,收集并生成日志后,分别远程调用和本地调用部署与EJB服务器的日志处理器方法,日志处理器采用JMS异步消息机制发送消息给日志输出组件,然后直接完成接收处理,具体的日志记录工作由日志输出组件异步完成。
l 部署与EJB服务器的日志检索接口提供各种纬度的日志检索服务,WEB端远程调用EJB的日志查询方法查询并获得所检索的日志。
l 日志定期清除机制,采用定时伺服方式运行于服务器,设定一段时间周期执行一次日志清理操作。
2. 日志记录功能
具体实现如下图
l 拦截器拦截方法统一收集系统中方法执行的痕迹、信息、调试、错误和致命错误的日志信息。
l 开发人员可以根据自己的需要手工编码,在程序方法体内部记录信息、警告、调试和数据日志。
l 日志收集器调用日志处理器相应分类的日志处理方法,日志处理的方法整理日志信息,最终通过消息发送器将日志消息统一发出。
l 日志输出器有一个监听器和输出部件组成。监听器监听外部发送的日志处理消息,得到消息后调用输出部件write()对日志进行记录。对于日志输出部件的write接口的实现类,为节省系统资源,考虑是否使用静态方法或单例模式?
l EJB端日志生成器采用EJB3.0方法拦截方式实现,拦截方法调用的过程,并通过自定义的注解获得方法的相关描述信息,组装为日志对象。而后直接调用日志处理器的方法,处理日志信息。EJB端日志收集器与日志处理器直接紧耦合,不通过接口调用。
l WEB端日志生成器,采用Seam拦截进行收集,封装为日志对象,通过远程调用发送EJB服务器日志处理器进行处理。
l 封装统一的日志对象OECPLogItem实体bean作为日志传递载体,包含以下信息:
Ø 日志id
Ø 日志分类(track、info、error、data、warning、debug、fatal)
Ø 记录时间戳
Ø 所属模块
Ø 调用类名称
Ø 调用方法名称
Ø 操作人id
Ø 用户操作事件
Ø 线程号
Ø 当前业务类型
Ø 当前单据类型
Ø 描述信息
Ø 异常错误栈信息
Ø 业务数据(实体bean对象,数据日志使用)
注:日志实体中的id、分类、时间、类名称、方法名称、线程号、描述信息为必填字段。其他字段可选,如:操作人id、操作事件、业务类型仅有web端可以得到,EJB端日志不记录这些字段信息。
l 日志处理器OEPCLoggerRemot暴露比较简单的接口和方法提供给日志生成器使用。虽然日志对象中已经封装有日志分类,但这里依然提供公开分类别的日志处理接口,为了增加客户代码调用处的易读性并减少代码重复:
Ø public void track(OECPLogItem log) //痕迹日志,用户操作以及操作引起的方法流转
Ø public void info(OECPLogItem log) // 信息日志,记录方法调用的信息完成情况信息
Ø public void error(OECPLogItem log) //错误日志,记录系统异常和业务异常信息
Ø public void data(OECPLogItem log) //数据日志,需要记录业务数据修改过程的数据
Ø public void warning(OECPLogItem log) //警告日志,不影响系统和业务完成的不正常的信息
Ø public void debug(OECPLogItem log) //调试日志,协助开发人员调试检查程序的日志信息。
Ø public void fatal(OECPLogItem log) //致命日志,系统发生致命错误时的日志信息,如内存溢出。
l 封装统一日志输出接口OECPLoggerWriterItf,仅暴露一个write方法,各种类型的日志对象全部通过这一个入口传入日志输出器。输出器接口实现可插拔,系统提供默认输出器实现类,统一将各类日志保存到数据库,如需要不同类别日志不同输出处理方式,可自行实现输出器接口,并修改EJB部署文件覆盖默认配置,EJB容器自动检测系统配置注入需要的实现类:
Ø protected void write(OECPLogItem log)
l 日志记录拦截器编码细节
Ø 日志记录拦截器拦截到方法后,首先以记录一次track日志,XX方法被调用,而后执行拦截的方法,方法执行完成后,记录debug日志XX方法运行结束耗时n-MS,并记录info日志XX方法执行成功。如果方法执行发生异常,则抓住异常,根据错误类型Exception还是Error,记录error日志或是fatal日志,然后封装并抛出异常。
l 为辅助日志输出,添加两个自定义注解。
Ø OECPLogMethodDescription :用于描述方法的与日志记录相关的信息,如方法执行成功的描述,执行失败的描述。
注 :OECPLogMethodDescription方法描述,只在拦截器记录日志时用于拦截器解析被拦截方法的描述,程序员人为记录日志时该注解的信息,不被记录。
3. 日志检索机制
提供按照日志对象中所有字段信息的多维度组合日志查询。此功能为普通单表查询无需复杂设计。
4. 日志清理机制
由于日志记录是非常频繁的事情,所以系统日志表的膨胀速度一定很快,甚至比业数据表要快得多。如果数据量过大,会造成系统资源的过度占用,也会影响检索效率。而且,保留过多过期的系统运行或者调试日志基本上是没有用处的。因此必须要有日志定期清理机制,定期对日志进行清理。
l 采用EJB3.0定时机制实现,日志清理避开业务高峰时段,一般定制在深夜进行。
l 清理规则(待定):
Ø 根据日志记录时间,对超过2个月的日志进行清理。
Ø 清理周期可设置。
5. 日志组件异常
l 日志组件本身也有可能发生异常,导致系统的日志无法记录。在这种情况下,日志组件将自身发生的异常信息记录到文本文件,以方便排错调试。
四、使用方法
1. 每个方法可添加有OECPLogMethodDescription注解,用来描述此方法执行成功和失败的描述消息,如无注解,默认成功时描述为“执行成功!”,失败时为“执行失败!”。目的是为了在使用拦截机处理相应的日志信息时,特别是异常日志时,通过该方法的日志描述,来给客户端输出友好的提示信息。
2. 不需要被拦截并记录日志的类或者方法使用@ExcludeDefaultInterceptors注解来避开默认拦截机。
3. 每个ejb项目包下必须包含部署文件ejb-jar.xml文件,该文件必须包含<display-name>节点描述模块名称。
4. ejb-jar.xml中设定默认日志拦截机,配置如下:
<assembly-descriptor>
<interceptor-binding>
<ejb-name>*</ejb-name>
<interceptor-class>org.oecp.commons.log.OECPLogInterceptor</interceptor-class>
</interceptor-binding>
</assembly-descriptor>
五、UML图
上图为该组件核心类图,以竖线分割开,左侧为各组件内部需要使用各种java类,内部session bean使用本地接口。在各组件中,日志组件只负责得到各种日志信息(通过拦截机获取或者由开发人员在适当的方法中调用),并且将该日志信息通过发送器发给JMS。为了保证日志的统一管理和系统运行的效率。保存日志信息的功能不提倡在各个组件中进行存储,而是由平台统一进行存储和管理。同时为了考虑系统的运行效率,日志的处理采用异步的处理方式,保证了不会因为日志的存在而降低业务运行的效率。竖线右侧接收日志信息并存储该日志信息,该部分的功能应该处在运行平台的EJB容器中或者单独的消息服务器中,如果处在运行平台的EJB容器中,写日志接口可采用本地接口,以提高效率。如果单独的消息服务器,写日志的接口则要采用远程接口,将日志持久化到平台的数据库中。