徐萌飛 王軍玲
( 中國船舶重工集團公司第七一二研究所,武漢 430064 )
日志功能作為軟件系統的重要組成部分,在記錄軟件運行狀態、調測和故障定位方面發揮著重要的作用。在船舶系統中,控制系統要長期頻繁地和其他子系統進行信息交互。控制系統除了發出控制指令,還要從事被控制對象的狀態信息采集,檢測控制指令的執行情況等多個動作,觸發事件多,實時性要求高,所以準確高效的日志是控制系統必需功能。
在控制系統軟件的設計過程中,為滿足實際運行需求,日志功能要經過全面設計,使用高效、可靠的實現方式,才能夠在實際運行中充分發揮其功用。本文將對日志記錄功能的軟件設計的若干要點及其實現方法進行探討,為后續軟件日志功能的設計和開發工作提供參考。
在軟件中,一般采用統一的日志記錄函數來完成日志功能,或者使用統一的對象來處理日志。要盡量避免在每一處需要記錄日志的地方單獨使用代碼實現日志功能。
使用統一的日志模塊具有如下優勢:
(1)減少無價值的重復開發量
在日志中,每記錄一條日志都單獨編寫日志代碼,造成代碼冗余。而反復調用基本的輸入輸出語句(如文件的打開和關閉),導致效率低下。
(2)因需求變化導致的修改量小
采用共用日志模塊函數或對象方法,對日志記錄格式或者內容提出了新的需求,只需要一處修改,即可全面生效。而單獨使用代碼記錄的,可能需要處處修改。
日志作為記錄程序運行過程的載體,必須能夠完整記錄下運行時間,運行對象,執行動作,執行結果,執行前狀態量,執行后狀態量等信息,這樣在發生故障后才能追根溯源。國外已經有很多成熟軟件和相應的技術規范。
以UNIX下的syslog為例,它已經形成了一種事實上的工業標準,其日志記錄格式按RFC3164 (The BSD log protocol)定義,并對消息頭部進行擴展,其格式為:
<優先級>時間戳 主機名 模塊名/級別/信息摘要:內容
<priority>time stamp sysname module/level/digest:content
控制系統軟件的日志格式,可以參考這類方式實現。
在以往的日志功能設計過程中,往往容易遺漏對代碼執行前狀態量的記錄,導致在回溯日志時會凸現其缺陷。例如:系統發生了無法重現的問題,如果從代碼邏輯上檢視不出問題,而又缺乏對執行前系統狀態量的記錄,就不能確定代碼執行前系統是否處于正常工作狀態,也無法定位是哪部分子系統故障或者硬件功能失效。
在主控系統和各個子系統共同運行的情況下,尤其是聯調過程中,各個子系統不能因為主系統在記錄日志信息就放棄自身的日志功能。因為在主控系統和子系統接口信息傳遞過程中,子系統日志和主控系統日志之間可以相互印證。缺少了子系統的日志作為判斷依據,會增加對消息傳遞通道以及子系統接口功能模塊的檢查成本。
控制系統的動作往往是具有時序性的,而且集散控制系統中,更是采用集中管理、分散控制,所以保證日志記錄精度對后期分析尤為重要。
當控制系統啟動后,一個重要的原則是必須有一個可靠的時間基準,即統一的時間源。在多個子系統的動作具有時序關系或者相互影響的情況下,如果沒有統一的時間基準,將無法根據日志確定故障發生的時間,以及確定當時各個系統所處的狀態,更無法找到問題發生的根源。
建立統一時間基準的方法有多種,在系統要求不高的情況下,一般可以利用系統內部的晶振時鐘。精度要求高的情況下,可以引入外部高精度時鐘。外部時鐘的引入,可以通過外部時鐘硬件,也可以通過網絡獲取標準時間。
在與外界通訊隔絕的封閉式系統中,由于沒有獲取標準時間的條件,則需要盡可能將系統(一般是主控系統)的初始上電時間作為其內部時鐘的日志記錄起點時間。各子系統上電后也要通過通訊接口與主控系統進行時鐘同步,來保證后續日志記錄在時間維度上的一致性。
在控制系統對處理精度要求較高的情況下,有的子系統本身會帶有時鐘,雖然子系統可以靠自身的時鐘來記錄時間,但是在進行業務循環時,子系統仍需要在每次業務處理開始時,和主控系統進行一次時間再同步。如果不做這個同步的動作,那么在子系統時鐘和主控系統時鐘精度不同的情況下,隨著業務循環次數增加,時間偏差將積累擴大,一旦經過多次業務循環后發生故障,則兩個系統之間日志記錄的時序匹配關系會發生偏差,會很大程度上干擾問題定位。
在故障日志記錄的功能設計中,都需要對錯誤進行編號和分類工作,分類的依據有多種,例如有的系統按不同軟件模塊產生的錯誤進行編號,有的按不同錯誤類型進行分類編號。開發人員定義了錯誤號后,在內部要保留一份錯誤號列表,便于故障出現后的快速檢索。例如可以使用如表1格式:

表1 故障日志中錯誤的編號和分類
尤其需要注意的是,只要是能夠詳細區分的錯誤,應當使用不同的錯誤號進行標識。否則故障發生后,沒有唯一的故障原因與之對應,會給定位帶來冗余的工作量。
最后,在多個進程同時運行的情況下,建議每個進程分別記錄自己的日志。如果多個進程使用同一個日志文件,往往導致資源沖突,并且各個進程記錄的日志相互交錯,不便于日后查看。
由于日志記錄了整個業務運行流程,所以未經加密保護的日志可能被逆向分析或者被篡改,從而導致系統架構和設計方面的泄密,甚至導致軟件系統被攻擊,給廠商帶來安全隱患和知識產權隱患。例如在電信行業曾經出現過由于充值日志被逆向分析,導致充值卡被偽造盜用的情況。其次,由于日志透露了詳細運行信息,尤其是故障發生后,日志記錄功能為了便于后續定位,往往全面記錄故障發生時系統狀態和故障產生原因,一旦被他人獲取,可能給相關廠商帶來商業上的糾紛和損失。例如某些市場關系不好的客戶會根據日志提供的信息夸大造成的損失向供應商提出索賠。在軍用方面,日志的泄露,不僅會導致系統設計缺陷暴露,還可能導致軍用設備運行狀態失密。例如,如果未被加密的日志中記錄了設備運行周期時間或GPS定位的位置信息,則一旦信息流失,可能導致設備性能參數被泄露或船舶運行航線被暴露。
故此,很多軟件中也開始增強了對日志的保護。不少廠商采用加密方式記錄日志,用普通軟件無法查看其內容,必須通過廠商提供的專有軟件進行解密處理。
控制系統運行時,產生的日志和告警信息很多,如果都向上位機控制臺轉發,存在如下弊端,
(1)重要信息往往被海量的普通信息所掩蓋,無法引起運維人員關注,增大了系統失控的概率;
(2)日志中大量出現的告警會引起用戶的反感,甚至給客戶帶來軟件質量不穩定的感覺。
所以,日志記錄使用分級和開關控制顯示已成為軟件設計的必要因素。國外成熟的系統軟件,都對日志和告警進行了分級定義,仍以syslog為例,將日志分為八種安全級別,分別代表不同的含義,如表2。
對于日志級別為0-3的消息,系統應該以非常明顯的顯示方式和提醒方式將信息傳遞給運維人員,例如用紅色或大字體傳遞到上位機,同時發出聲音告警,甚至可以發送警告短信到運維人員手機,啟動呼叫中心來呼叫運維人員。

表2 八種安全級別日志的含義
對于3級以下的信息,可根據實際需求,使用開關進行控制日志顯示。例如處于5級的通知信息,就可以考慮在平時關閉顯示開關而不上傳到上位機,僅錄入日志文件。處于6-7級的信息,只在軟件調測或聯調故障定位時才打開記錄開關,平時運行時關閉,軟件不會把調試信息記錄到文件中,從而節省了存儲空間和處理時間。
另外,隨著積累的日志不斷增加,未來對數據進行整理和分析的需要也會產生。對于長期運行的控制系統,建議在記錄日志信息時使用標準化的格式,便于后續使用分析軟件做數據挖掘和智能分析。
由于控制系統軟件不是孤立對象,其運行與操作系統和系統所在的硬件平臺有密切的關系,尤其是嵌入式系統,更需要仔細分析。
雖然目前計算機的存儲能力在不斷增強,但在很多情況下,從設備占用空間和成本考慮,還是有不少系統提供的存儲空間容量有限。如一些ARM板上使用的存儲芯片,只有幾 k的存儲空間,一旦記錄內容過多,就可能造成空間不足而記錄失敗。在這種情況下,要考慮精簡日志內容或者使用壓縮編碼格式減少空間占用。
此外,基于某些操作系統運行的軟件,如果長時間記錄日志,會導致日志文件過大,延長了后續寫日志時每次打開文件的速度。甚至當文件大小超過了操作系統可以支持上限時,(例如某些版本的UNIX文件,如果不打開大文件開關,會有2G大小的限制),可能會發生日志文件被破壞或者日志記錄丟失的情況。
故此,對長期運行且產生日志數據量大的系統,要設計定期備份和壓縮打包機制,將歷史日志記錄轉存到系統外大容量介質(如磁帶機)上。
一些存儲芯片或者操作系統,在每次重新記錄數據時,使用的寫入機制不是清空機制而是復寫機制。即存儲介質上的舊數據不被清除,而使用從首地址記錄,用新記錄來覆蓋掉原記錄。例如在某嵌入式軟件設計中,使用了flash芯片做日志存儲,整個業務處于不斷循環中。在每次循環開始時,都重新記錄日志。由于使用了覆寫機制,剛開始多次業務流程都是成功的,但在某次業務循環過程中發生了故障,系統日志記錄也被中斷,結果發現舊的運行成功的日志記錄的后半部分和新的業務運行的前半部分混淆在一起。加上只在每次業務循環開始時加上了時間戳,在后續日志記錄過程中沒有繼續加時間戳。故此,當故障發生后,定位缺陷時,查看日志仍以為是一份成功的日志,無法確認程序運行時發生故障的時間以及存在缺陷的代碼。如圖1所示。

圖1 存在缺陷的日志
為避免這種情況發生,一般建議在記錄每條日志信息時,帶上時間戳標記。如圖2所示。
如果實在無法建立時間戳標記,可以通過日志流水號來區分。具體做法是,在存儲區使用一個專用地址區來記錄當前最大的日志流水號。在每次需要記錄日志時,將最大流水號累加,更新到專用地址區,此最大流水號也包含在日志記錄中。這樣即使采用覆蓋式寫入的日志內容,也能夠通過不同的流水號段來區分,如圖3所示。

圖2 帶時間戳標記日志

圖3 流水號日志
日志模塊對此部分過程處理的流程圖如圖4。
在很多實時控制系統中,由于系統硬件性能的限制,隨著日志內容的增加,記錄的讀寫速度會越來越低,而且頻繁打開和關閉文件也會占用系統資源。在這種情況下,也要考慮精簡日志,或者通過日志開關減少不重要的日志記錄動作。
可以考慮的一種設計是,在系統具有富余內存時,可借助更高性能的內存來提高日志記錄效率。例如程序在進行業務流程循環時,如果內存足夠大,可以申請一塊內存作為內存日志記錄區。每次業務流程開始后,通過日志開關控制將當前日志信息寫入內存日志區,等業務流程處理結束時,切換日志開關,一次性將內存日志記錄到硬盤中,形成硬盤上的日志文件。當業務流程處理過程中發生中斷或者錯誤時,也通過控制參數調用日志記錄函數或方法,一次性將內存日志記錄到硬盤中。日志函數的流程圖如圖5。
這樣處理的方式有幾個優點:
1) 寫入內存速度比硬盤速度高近百倍,因此整個日志記錄過程速度會更快。
2) 一次性寫入日志到日志文件,減少了頻繁打開關閉日志文件的次數,提高了處理效率;
3)節約了硬盤空間,因為在業務運行正常的情況下只記錄關鍵信息,不必記錄過多細節。
4)在業務流程處理失敗的情況下保留了足夠多的用于故障定位的日志信息。

圖4 流水號日志的流程圖
這種處理方式的唯一風險是系統突然掉電導致內存日志區的數據丟失。折中的處理辦法是每次在業務循環處理中的關鍵節點處將內存日志區的內容寫入硬盤中,適當犧牲小部分性能來換取可靠性。
日志的主要功能是記錄系統運行狀態以及為調測和故障定位提供必要信息,是軟件設計中需要考慮的重要部分。在進行日志功能設計時,除了常規的運行記錄需求外,還必須結合軟硬件環境和特定客戶需求,從內容、格式、實現方法等多方面考慮,才能在后續的調測、聯調、正式運行中發揮功用。本文中提到的日志功能的設計要點及實現方式,在控制系統的軟件設計上是需要經常加以關注的。

圖5 日志函數的流程圖
[1]www.w3.org. RFC3164. 2001年8月.
[2]謝美意 朱虹 馮玉才. 自修復數據庫系統日志機制研究. 計算機科學 2010年4月.
[3]王偉 楊永川. Windows Vista系統日志文件格式分析及數據恢復. 計算機安全, 2009年4月.
[4]JIM TURNER. 確保成功地備份事件日志的一種方法. Windows IT Pro magazine, 2008年8月.
[5]李甜. 基于 Syslog的日志審計系統的研究和實現.中國新通訊, 2008年9月.