摘 要:微軟公司隨VISUAL STUDIO 2005 發(fā)布了.NET 2.0,該系統(tǒng)較.NET 1.1增加了大量實(shí)用方便的功能和新特性,跟蹤系統(tǒng)部分同樣大大增強(qiáng)了功能。如何用好這些功能,特別是定制這些功能,使之更好地為我們服務(wù),成為我們的當(dāng)前任務(wù)。本文將探討一下這方面的問題。
關(guān)鍵詞: TraceSource類 Switch類 StopWatch精確度 堆棧信息 線程 進(jìn)程 遠(yuǎn)程過程調(diào)用
一、跟蹤系統(tǒng)概念介紹
在.NET 2.0中,除了用來在程序中輸出調(diào)試和跟蹤信息的Debug類和Trace類外,還有TraceSource類。TraceSource類是.NET 2.0新添加的類,微軟公司建議在新開發(fā)的程序中使用TraceSource類來產(chǎn)生調(diào)試跟蹤信息。調(diào)試開關(guān)用來過濾信息的輸出,跟蹤偵聽器(TraceListener)類用來向外界真正輸出調(diào)試信息。所有類的參數(shù)都可通過程序配置文件進(jìn)行設(shè)置,而不需要重新編譯程序,修改程序配置信息即可改變?cè)摮绦虻恼{(diào)試跟蹤信息輸出的行為。
跟蹤偵聽器用來收集、存儲(chǔ)和路由跟蹤消息,是真正對(duì)外輸出日志信息的類對(duì)象。系統(tǒng)提供的跟蹤偵聽器往往不能滿足我們?cè)趯?shí)際應(yīng)用程序中的需要。例如,在無人職守環(huán)境下,服務(wù)程序的執(zhí)行日志需要輸出到能夠自動(dòng)切換的具有固定大小的日志文件中,而以上系統(tǒng)提供的跟蹤偵聽器均無法完成該任務(wù)。此時(shí)需要制定自己的跟蹤偵聽器。在使用Debug、Trace和TraceSource對(duì)象時(shí),系統(tǒng)可根據(jù)配置信息為以上對(duì)象生成跟蹤偵聽器。如果沒有配置信息,系統(tǒng)將會(huì)為Debug和Trace生成缺省的跟蹤偵聽器(DefoultTraceListener),該偵聽器將消息發(fā)送到調(diào)試器的消息窗口(Visual studio的debug窗口)。系統(tǒng)還提供了EventLogTraceListener、ConsoleTraceListener、TextWriteTraceListener、DelimitedListTraceListener和XmlWriterTraceListener跟蹤偵聽器。Debug、Trace和TraceSource所使用的跟蹤偵聽器被放在各自的Listeners屬性中,可通過編程來訪問和維護(hù)該偵聽器列表。
所有的跟蹤偵聽器均派生自TraceListener類,該類是抽象類,在用戶制定的派生類中,需要重載和消息輸出有關(guān)的函數(shù)包括:TraceData(),TraceEvent(),Write(),WriteLine(),F(xiàn)ail()。這些函數(shù)負(fù)責(zé)接受其它程序部件調(diào)用,輸出跟蹤消息。
TraceListener類還實(shí)現(xiàn)了IDisposable接口,該接口中的Dispose()和Finalize()被用來關(guān)閉和釋放資源。
完成自定義跟蹤偵聽器后,可將該偵聽器放在單獨(dú)的動(dòng)態(tài)鏈接庫中,方便其它程序共享該組件。實(shí)例化該偵聽器,可通過配置文件來完成,也可通過編程,手工實(shí)例化偵聽器對(duì)象,再將偵聽器添加到TraceSource或Trace的Listeners屬性中。
二、定義自己的跟蹤開關(guān)類
跟蹤開關(guān)用于啟用、禁用和篩選跟蹤輸出。它們是存在于代碼中的對(duì)象,可以通過.config文件從外部進(jìn)行配置。.NET Framework中提供了三種類型的跟蹤開關(guān):BooleanSwitch類、TraceSwitch類和SourceSwitch類。BooleanSwitch類用作切換開關(guān),可啟用或禁用各種跟蹤語句。TraceSwitch和SourceSwitch類用于為特定的跟蹤級(jí)別啟用跟蹤開關(guān),以便顯示為該級(jí)別及其下的所有級(jí)別指定的Trace或TraceSource消息。如果禁用此開關(guān),則不會(huì)顯示跟蹤消息。
跟蹤開關(guān)作用于TraceListener類,通過跟蹤偵聽器的Filter屬性,可訪問該偵聽器的跟蹤開關(guān)對(duì)象。當(dāng)系統(tǒng)提供的跟蹤開關(guān)類不能滿足程序要求時(shí),需要開發(fā)用戶自己的跟蹤開關(guān)。所有的跟蹤開關(guān)對(duì)象都派生自Switch類,派生類中必須實(shí)現(xiàn)SwitchSetting方法和OnValueChanged方法。Switch類的Value屬性用來設(shè)置或返回用來過濾消息的值的字符串。對(duì)Value屬性的賦值將會(huì)調(diào)用OnValueChanged方法,在該方法中,需要將字符串的Value值轉(zhuǎn)換成int型的值,用來設(shè)置SwitchSetting屬性。
同用戶自定義跟蹤偵聽器一樣,建議將用戶自定義跟蹤開關(guān)放到單獨(dú)的動(dòng)態(tài)鏈接庫中,方便不同的應(yīng)用程序共享該組件。同樣,可通過配置文件,為跟蹤偵聽器對(duì)象生成對(duì)應(yīng)的跟蹤開關(guān)對(duì)象。
三、用StopWatch來精確測(cè)量代碼執(zhí)行的時(shí)間
通常,我們根據(jù)經(jīng)驗(yàn),測(cè)量一段代碼的執(zhí)行時(shí)間,是在代碼的開始位置記錄當(dāng)前的系統(tǒng)時(shí)間,在代碼結(jié)束時(shí),也記錄一次系統(tǒng)當(dāng)前時(shí)間,再比較這兩個(gè)時(shí)間,獲得這兩個(gè)時(shí)間之間的差別,精確到毫秒。
但本方法存在以下問題:首先是精度不夠。當(dāng)代碼執(zhí)行時(shí)間在毫秒以下時(shí),無法測(cè)量時(shí)間。其次,系統(tǒng)的當(dāng)前時(shí)間的更新間隔并不能達(dá)到毫秒級(jí),而是1/18秒。這樣,以上方法的實(shí)際測(cè)量精度是1/18秒,精度太低。
為解決以上問題,.NET Framework 2.0中提供了Stopwatch類。該類提供了一組屬性和方法,能夠精確測(cè)量代碼的執(zhí)行時(shí)間。Stopwatch實(shí)例可以測(cè)量一個(gè)時(shí)間間隔的運(yùn)行時(shí)間,也可以測(cè)量多個(gè)時(shí)間間隔的總運(yùn)行時(shí)間。在典型的Stopwatch方案中,先調(diào)用Start方法,然后調(diào)用Stop方法,最后使用Elapsed屬性檢查運(yùn)行時(shí)間。
Stopwatch類的Frequency屬性是以每秒刻度數(shù)表示的計(jì)時(shí)器頻率,ElapsedTicks屬性是獲取當(dāng)前實(shí)例測(cè)量得出的總運(yùn)行時(shí)間(用計(jì)時(shí)器刻度表示)。其中計(jì)時(shí)器刻度是指Frequency分之一秒的時(shí)間間隔。當(dāng)然,也可通過Elapsed方法獲得TimeStamp對(duì)象,來獲得100毫微秒為單位的計(jì)時(shí)器刻度(Ticks)時(shí)間。
四、利用StackTrace獲取函數(shù)調(diào)用堆棧信息
在設(shè)計(jì)應(yīng)用程序的日志系統(tǒng)時(shí),打印函數(shù)的進(jìn)入和退出記錄,是經(jīng)常需要使用的功能。此時(shí)獲取當(dāng)前函數(shù)的名稱以及所在類的名稱,甚至函數(shù)調(diào)用堆棧信息,是必須作為日志信息輸出的內(nèi)容。如何實(shí)現(xiàn)以上功能呢?
.NET Framework的StackTrace類,屬性以逆向時(shí)間順序列出了方法調(diào)用,即描述了最近的方法調(diào)用,還為堆棧上的每個(gè)方法調(diào)用都列出一行堆棧跟蹤信息。如果在編譯時(shí)生成了調(diào)試符號(hào),調(diào)試符號(hào)包含在構(gòu)造StackFrame和StackTrace對(duì)象時(shí)使用的文件、方法名、行號(hào)和列信息這些數(shù)據(jù)中的大部分。但是,由于優(yōu)化期間發(fā)生的代碼轉(zhuǎn)換,StackTrace屬性報(bào)告的方法調(diào)用可能沒有預(yù)期的多。
StackTrace是一個(gè)包含StackFrame對(duì)象的堆棧,堆棧最上面的是最近發(fā)生的函數(shù)調(diào)用信息。StakFrame對(duì)象中包含函數(shù)名稱,文件名稱,行號(hào),函數(shù)參數(shù)等信息。應(yīng)用程序可在需要的地方生成StackTrace對(duì)象,然后遍歷該堆棧,以輸出程序執(zhí)行路徑相關(guān)的跟蹤消息。
五、利用CorrelationManager對(duì)象在不同線程和進(jìn)程間保持調(diào)用線索和相關(guān)數(shù)據(jù)
在分布式應(yīng)用程序中,一個(gè)方法的執(zhí)行,會(huì)延續(xù)到另外進(jìn)程的方法中,如何關(guān)聯(lián)不同進(jìn)程中的方法執(zhí)行線索,成為日志系統(tǒng)設(shè)計(jì)時(shí)的一個(gè)棘手問題。
.NET framework為我們提供了一個(gè)方便的解決方案——CorrelationManager對(duì)象。該對(duì)象用來關(guān)聯(lián)同屬于某個(gè)邏輯事務(wù)的多個(gè)跟蹤,可以使用唯一操作的標(biāo)識(shí)對(duì)通過單個(gè)邏輯操作生成的跟蹤進(jìn)行標(biāo)記,以將其與通過其他邏輯操作生成的跟蹤區(qū)分開來。例如,在基于報(bào)文處理的應(yīng)用程序中,可利用此功能,通過以不同的報(bào)文號(hào)對(duì)操作進(jìn)行標(biāo)記,以區(qū)分和關(guān)聯(lián)不同報(bào)文在不同進(jìn)程和函數(shù)中產(chǎn)生的日志信息。
邏輯操作也可以嵌套。LogicalOperationStack屬性公開嵌套的邏輯操作標(biāo)識(shí)的堆棧。對(duì)StartLogicalOperation方法的每個(gè)調(diào)用都會(huì)將一個(gè)新的邏輯操作標(biāo)識(shí)壓入堆棧。對(duì)StopLogicalOperation方法的每個(gè)調(diào)用都會(huì)從堆棧中彈出一個(gè)邏輯操作標(biāo)識(shí)。邏輯操作標(biāo)識(shí)是對(duì)象。
六、截獲和記錄遠(yuǎn)程過程調(diào)用
在進(jìn)行遠(yuǎn)程過程調(diào)用時(shí),函數(shù)調(diào)用方和函數(shù)被調(diào)用方都需要對(duì)該過程進(jìn)行記錄,該功能是日志系統(tǒng)應(yīng)該具備的基本功能。要實(shí)現(xiàn)自動(dòng)記錄該過程,顯然需要截獲所有的遠(yuǎn)程過程調(diào)用。
在客戶端,有兩種方法能夠截獲對(duì)遠(yuǎn)程對(duì)象的方法的調(diào)用。第一種方法,使用用戶制定的RealProxy類。在創(chuàng)建遠(yuǎn)程對(duì)象的本地代理類時(shí),直接創(chuàng)建用戶制定的RealProxy對(duì)象,再用RemotingServices對(duì)象的Connect方法創(chuàng)建一個(gè)系統(tǒng)提供的RealProxy對(duì)象。當(dāng)用戶調(diào)用遠(yuǎn)程對(duì)象的方法時(shí),調(diào)用請(qǐng)求被轉(zhuǎn)給了用戶定制RealProxy的Invoke方法,在該方法中,可加入跟蹤消息輸出代碼,輸出方法調(diào)用的詳細(xì)信息,如:遠(yuǎn)程對(duì)象名稱、方法名稱、參數(shù)值等。在用戶制定的RealProxy中,使用函數(shù)調(diào)用的消息數(shù)據(jù),調(diào)用實(shí)際的RealProxy對(duì)象的對(duì)應(yīng)函數(shù),再將返回的值封裝成ReturnMessage對(duì)象,返回給實(shí)際調(diào)用函數(shù)。在退出Invoke方法前,還可向日志系統(tǒng)輸出函數(shù)調(diào)用返回值,或函數(shù)調(diào)用出錯(cuò)的詳細(xì)信息,遠(yuǎn)程過程調(diào)用執(zhí)行時(shí)間等信息。第二種方法是制定ClientChannelSink類和ClientChannelSinkProvider類。在生成新的客戶端通道時(shí),由用戶制定的ClientChannelSinkProvider類對(duì)象來生成ClientChannelSink類,并將其掛接到客戶端的Remote.NET體系中的客戶端的信道接收鏈的插接點(diǎn)上。這樣在客戶制定的ClientChannelSink的類對(duì)象的ProcessMessage方法中,可添加代碼,檢查每次函數(shù)調(diào)用的詳細(xì)數(shù)據(jù),并輸出到日志系統(tǒng)中。同樣,也可在函數(shù)調(diào)用返回時(shí),打印函數(shù)返回結(jié)果的詳細(xì)信息到日志系統(tǒng)中。
在服務(wù)端,也可采用同客戶端類似的處理方式。生成用戶定制的ServerChannelSink類和ServerChannelSinkProvider類,在ServerChannelSink類的ProcessMessage方法中,輸出日志信息。
七、捕獲未被處理的異常
在程序執(zhí)行過程中,需要經(jīng)常檢查代碼執(zhí)行的結(jié)果,特別是要注意捕捉適當(dāng)?shù)漠惓!5傔€是有未被捕捉到的異常被拋出,使程序被非正常終止。將所有的異常都捕捉到,并詳細(xì)記錄這些異常的相關(guān)數(shù)據(jù),成為了日志系統(tǒng)應(yīng)該具有的功能。
在應(yīng)用程序所在域中,有一個(gè)UnhandledException事件,該事件是在應(yīng)用程序當(dāng)前域中出現(xiàn)了未被捕獲并處理的異常時(shí)被觸發(fā)。我們可以在應(yīng)用程序所在的域?qū)ο笊希ㄟ^處理UnhandledException事件,打印未處理異常的詳細(xì)信息到日志系統(tǒng)中,為最終處理這些異常提供方便。
參考文獻(xiàn):
[1]Andrew Haigh著.賈愛霞譯.面向?qū)ο蟮姆治雠c設(shè)計(jì).北京:機(jī)械工業(yè)出版社,2003.
[2]黃忠成.Framework的設(shè)計(jì)與應(yīng)用:基于Windows Forms的應(yīng)用開發(fā)實(shí)踐.電子工業(yè)出版社,2006.
[3]Chappell,D著.榮耀譯..NET大局觀.電子工業(yè)出版社,2006.