項目背景:
DRAGON是供情報機關等在電信電訊廠商查詢處客戶通訊記錄等的業務系統。因為會牽涉到電信客戶的隱私,所以要求對於使用者在系統內的所有增加/刪除/修改等操作,以及對於客戶資料以及通訊記錄等隱私內容的查詢和查看等操作記錄動作記錄並且可以通過頁面查詢這些動作記錄。動作記錄記錄的內容至少包括操作時間,操作人,操作對象的屬性等等。
為操作日誌的查詢頁面:使用者選擇object-type後,將在action下拉表中列出對應的action,選擇action之後,將列出該action需要記載的欄位,以供使用者查詢。如使用者選擇了updateWarrant這個action,將列出如warrantType, warrantId等欄位。
搜尋結果頁面:
實現思路:
此系統是通過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
/*<br /> * Data Retention and Guardian Online<br /> *<br /> *<br /> * Copyright (C) 2006-2012 Hewlett-Packard Company<br /> */<br />package com.hp.dragon.business.formats.interf;<br />public interface FormatProviderInterf {<br /> public String format(Object data, String formatString) throws BusinessException;<br />}<br />
3. Spring的設定檔:
<bean id="advisorEventManager" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"><br /> <property name="advice"><br /> <ref bean="logEventService" /><br /> </property><br /> <property name="patterns"><br /> <list><br /> <value>.*/.create.*</value><br /> <value>.*/.insert.*</value><br /> <value>.*/.update.*</value><br /> <value>.*/.delete.*</value><br /> <value>.*/.changeStatus.*</value><br /> <value>.*/..*Tracked</value><br /> </list><br /> </property><br /> </bean>
4. LogEventSeviceImpl.invoke()的Sequence圖:
對於不同的操作類型:如update操作,需要在beforeTracking()方法中reload更新前的對象,將更新前的屬性值儲存在一個局部對象中,然後在afterTracking()中再記錄下更新後的值,從而產生logevent的資訊。create/insert操作則需在afterTracking()記錄資訊,而delete則只能在beforeTracking()中記錄資訊。
5. 總結
日誌系統的任何錯誤或者異常都不可以影響業務的主流程。
由於DRAGON是以產品的形式出售給不同的電信電訊廠商,要求該日誌系統具有較高的可配置性以適應不同客戶的要求,所以系統中存在大量的可配置選項。
將配置資訊放在資料庫中,而不是使用annotation,是為了方便delivery team實施的方便-只需改改資料庫資料即可滿足不同電訊廠商的需求。
由於日誌數量巨大,所以建議定期刪除,至少在查詢的時候,只允許客戶查詢最新的資料。
註:日誌查詢和結果介面皆摘自HP-DRAGON產品的使用者文檔。