现在的位置: 首页 > 综合 > 正文

利用AOP实现业务日志系统

2013年08月21日 ⁄ 综合 ⁄ 共 3540字 ⁄ 字号 评论关闭

项目背景:

    DRAGON是供情报机关等在电信运营商查询处客户通讯记录等的业务系统。因为会牵涉到电信客户的隐私,所以要求对于用户在系统内的所有增加/删除/修改等操作,以及对于客户资料以及通讯记录等隐私内容的查询和查看等操作记录操作日志并且可以通过页面查询这些操作日志。操作日志记录的内容至少包括操作时间,操作人,操作对象的属性等等。

 

下图为操作日志的查询页面:用户选择object-type后,将在action下拉表中列出对应的action,选择action之后,将列出该action需要记载的字段,以供用户查询。如用户选择了updateWarrant这个action,将列出如warrantType, warrantId等字段。

    log search

搜索结果页面:

search result

实现思路:

    此系统是通过J2EE实现,遵从常规的 action->service->dao层次。

    (1) 因为所有的增删改以及某些查询/查看都需要记录日志,如果记日志的操作放在增删改的代码中,一来耦合严重,二来工作量巨大,所以采用Spring的AOP对Service层方法进行拦截,然后根据表达时, 如“user.name”来读取方法的参数或者返回值,从而生成业务操作日志。

   (2)记录操作日志的行为不可以影响业务流程,并且记录日志的IO操作也会带来性能问题,所以将采用异步写数据的方式,以及核心业务代码会生成logEvent的内容,然后通过JMS发送消息,MDB收到消息后再去写数据库。

    (3)用户的信息将放在threadLocal中。

 

具体实现:

  1. 数据库设计:

     1.1. entity: 保存系统中的实体类型相关的信息,如warrant, workitem等。具体字段如下:

  • entity_id: pk
  • entity_name: 实体类型的名称
  • ENABLE_FLAG:对于该类实体是否需要记录用户操作日志。 
  • description: 描述。

     1.2. action: 保存用户操作,如deleteWarrant等(和action是manyToOne的关系)

  • id_action: pk,
  • name: 操作的名称,
  • description: 描述,
  • entity_id: fk到entity,
  • action_type: 当前操作的类型,如删除/修改/增加等,对于不同的类型,获取数据的方式也不一样。
  • enable_flag:该操作是否需要记录日志。

    1.3. event:用来保存与action对应的方法名等的信息。(与action是manyToOne的关系。因为从用户角度的一个操作,如修改warrant数据,代码上可能会有多个方法与之对应,如updateWarrantType(), updateWarrantTime()等等。)

 

  • id_event: pk,
  • id_action: fk to action,
  • method_name:方法名称。
  • serviceClassname: service接口的className。
  • enable_flag: 是否对该方法记录日志
  • argClasses(varchar2(1024)):方法参数类型的full name用“,”拼接成的字符串。 该字段主要用于方法重载的情况,需要根据参数的类型来判断是否需要记录业务操作日志。

   1.4. action_detail: 需要在日志中记载的详细内容,如warrant的typeName等。

  • ID_ACTION_DETAIL:  PK
  • ID_ACTION:  FK to Action
  • DETAIL_NAME:字段的名称。
  • DETAIL_ORDER: 页面上该字段显示的顺序
  • PARAM_INDEX:该字段对应方法的参数的顺序(从0开始)。如果该字段取自方法的返回值,请设为-1.
  • DETAIL_VALUE_EXPRESSION: 取值表达式,如: user.name; users[0].name等。
  • ENABLE_FLAG: 该字段是否需要记录日志
  • FORMAT_PROVIDER_CALSS_NAME:实现了FormatProviderInterf接口的class的fullName。用于处理如: deleteWarrant(Long warrantId)的情况,我们需要根据先根据warrantId查询到warrant对象,然后才能记录其type等属性。再如根据表达式获得的值是date类型,我们需要先进行格式化后再显示给用户。这些操作都放在formatProviderInterf的实现类中。
  • FORMAT_PATTERN: format的格式,供FORMAT_PROVIDER_CALSS_NAME中指定的class使用。
  • UPDATE_FLAG: 当前字段是否需要记录update前后的值(主要用于update操作的情况)。
  • SEARCHABLE: 客户是否可以根据该字段查询操作日志。
  • DESCRIPTION:描述。

  1.5. log_event: 操作日志(不包括具体的内容)。

  • id_log_event: PK
  • event_date: 操作时间,
  • id_user:用户
  • id_action: fk to action
  • id_object:所影响的objec的id
  • object_name: 对象的名称。  
  • id_status_from: 变化前的状态。
  • id_status_to: 变化后的状态
  • crc: 根据时间,内容等生成的验证码,防止有人通过DB后台修改记录。

1.6. log_event_detail: 操作日志的详细内容。

 

  • id_log_event_detail: pk
  • id_log_event: fk至logEvent
  • id_action_detail: fk to action_detail
  • name: 字段名称,从action_detail中copy过来的冗余字段。
  • value : 字段的值
  • event_date:copy自log_event的冗余字段。
  • crc : 验证码。

 

2. 几个接口: 

    2.1. FormatProviderInterf

       

   3. Spring的配置文件:

       

4. LogEventSeviceImpl.invoke()的Sequence图:

invoke

对于不同的操作类型:如update操作,需要在beforeTracking()方法中reload更新前的对象,将更新前的属性值保存在一个局部对象中,然后在afterTracking()中再记录下更新后的值,从而生成logevent的信息。create/insert操作则需在afterTracking()记录信息,而delete则只能在beforeTracking()中记录信息。

 

5. 总结

日志系统的任何错误或者异常都不可以影响业务的主流程。

由于DRAGON是以产品的形式出售给不同的电信运营商,要求该日志系统具有较高的可配置性以适应不同客户的要求,所以系统中存在大量的可配置选项。

将配置信息放在数据库中,而不是使用annotation,是为了方便delivery team实施的方便-只需改改数据库数据即可满足不同运营商的需求。

由于日志数量巨大,所以建议定期删除,至少在查询的时候,只允许客户查询最新的数据。

 

注:日志查询和结果界面皆摘自HP-DRAGON产品的用户文档。

抱歉!评论已关闭.