計算安全的三個傳統的重要部分是機密性、完整性和可用性。在談到可用性時,我通常會討論那些比 較容易識別的威脅(例如,拒絕服務攻擊),但其實要實現可用性還遠遠不止於此。可用性要求很高的應 用程序必須有能力處理異常情況。如果出現問題,管理員必須能夠迅速選定一套解決方案。設計上考慮到 可管理性的應用程序通常具有更高的可用性,一定程度上是因為管理員能夠時刻了解到應用程序的運行狀 況。當問題確實出現時,可管理的應用程序會告訴管理員如何解決問題,而不是僅僅給出供開發人員分析 所需的堆棧跟蹤。
您也許對 Microsoft 倡導的動態系統管理計劃 (DSI) 有所耳聞 (microsoft.com/dsi)。此遠景使我 們能夠運用諸如系統定義模型 (SDM) 和服務模型語言 (SML) 的工具和語言來設計應用程序,構建出更加 容易部署和管理的應用程序。Visual Studio® Team System 提供的分布式系統設計器(原代號為 “Whitehorse”(msdn2.microsoft.com/ms181778.aspx))就是該設想發展方向的一個很好示 例,但這僅僅屬於早期的嘗試。目前開發人員需要一些實用的指導來構建可管理的系統。
IT 專業 人士也是用戶
我最近抽時間與 David Aiken 聊了聊,他是 Microsoft 的開發人員和技術宣傳師 ,對構建可管理軟件可以說是充滿激情。他的話真是一針見血:“開發人員應將 IT 專業人士視為 其軟件的用戶”。他指出在我們在設計 Windows 窗體或網頁的布局時付出了極大的努力,接著就提 出了這樣一個問題“我們為何不能在設計發布於事件日志的錯誤消息時也付出同樣的努力呢? ”。
David 確實言之有理。IT 專業人士不僅是軟件的用戶,而且要在軟件的整個生命周期 自始至終對其進行操作和管理。進一步簡化 IT 專業人士的工作,不僅從可用性角度看是非常重要,而且 系統管理員作為擁趸很可能有助於您產品的下一個版本的銷售。目前旨在促進可操作性的應用程序寥寥可 數,而這正是一個可以獲得競爭優勢的領域。
在過去幾年中,我目睹 Microsoft 在構建更加安全 的軟件(包括用於構建威脅模型的工具和藍圖)方面所取得的巨大進步。現在,由於在安全方面采取了更 加嚴格的控制,公司的重點似乎正在向可管理性方面轉移。如果幸運的話,在本文出版之際,您也許會聽 到 Microsoft 推出運行狀況建模工具的相關信息。
同時,我將介紹有關管理的最基本問題:事件 日志記錄。我將著重介紹如何更好地在 Windows Server® 2003 中使用 Microsoft® .NET Framework 提供的事件日志。
進一步了解事件日志
Windows® 事件日志在設計時始終 秉承本地化和高效的原則。例如,應用程序日志中設計良好的事件日志應包含足夠的文本信息以便向管理 員解釋出現的問題,同時提供建議性的操作。絕大部分這類文本信息都應該是已經本地化的,因為其讀者 很可能是位於其他國家/地區的遠程計算機用戶。為了使您能夠以母語閱讀日志,消息中的可本地化的文 本信息實際上並不存儲在日志中;而是在被讀取時與日志在本地進行合並。實際的文本信息存儲在讀者計 算機上已本地化的資源 DLL 中。這樣做還減小了日志文件的大小,同時減少了讀取這些信息所需的網絡 流量。
在 .NET Framework 出現之前,這對於開發人員而言非常清晰,因為他們必須自己創建並 注冊這些資源 DLL。資源 DLL 中的典型消息可能如下所示:“使用連接字符串 %2 連接數據庫 %1 失敗。請檢查數據庫是否可用。”
在本示例中,只有事件 ID、源、類型和少數其他元數據 以及在記錄事件時提供的參數等事件數據會存儲在事件日志中:數據庫名稱 (%1) 和連接字符串 (%2)。 在您使用諸如事件查看器的工具讀取此類事件時,Windows 會加載您的計算機上安裝的消息資源 DLL,根 據事件 ID 查找本地化消息,然後合並日志的參數以便為您顯示該消息。
Windows 通過查找日歷中的事件源名稱並將其映射到注冊表項來確定要使用哪個資源 DLL。該注冊表 項包含指向顯示消息應使用的資源 DLL 的路徑。圖 1 所示為一個事件源注冊表項的示例。在早期, Win32® 程序員會手動創建該注冊表項,但是 .NET Framework 的出現提供了一個 EventLog 類來隱 藏此類詳細信息。
圖 1事件源注冊
使用 .NET Framework 提供的事件日志
在 .NET Framework 1.x 版中,EventLog 類提供了對事件日志的基本訪問。該策略似乎想以簡化對事件日志的訪問來說服人 們使用事件日志。在安裝時,如果您的代碼以較高的權限運行,那麼僅需一行代碼即可創建一個事件源:
EventLog.CreateEventSource("MyApp","Application")
這樣,在運行時,如果代碼以較低的權限運行,則無需創建資源 DLL 就可以記錄事件:
EventLog.WriteEntry("MyApp", "Testing 123")
值得注意的 是,這裡涉及兩個步驟。請記住,在創建事件源時,實際上同時也在創建注冊表項。這是一個權限操作, 如果忽略這一步,EventLog.WriteEntry 會嘗試替您執行此操作。當您以管理員身份運行演示代碼時, EventLog.WriteEntry 可能會為您進行此操作,但當您使用普通用戶帳戶實際運行代碼時,則不會進行此 操作。所以如果您有操作權限,請確保在安裝期間創建事件源!
通常,如果在沒有相應的資源 DLL 時嘗試將消息寫入事件日志,事件日志會出現一條比較討厭的通知,抱怨無法找到事件的描述(請參 見圖 2)。對此,Framework 有獨特的處理方法。對 CreateEventSource 的簡單調用會將您的事件源與 默認的消息文件連接起來。該文件名為 EventLogMessages.dll,它包含了 65,536 個事件描述,每個描 述都包含字符串“%1”,為您希望寫入的任何字符串充當占位符。這樣做雖然可能會鼓勵更多 的人使用事件日志,但卻違背了將消息文件放在首位的初衷:允許以讀者的母語查看本地化消息,並將事 件日志的大小保持在控制范圍之內。
圖 2沒有消息文件 的事件
幸運的是,.NET Framework 2.0 版修復了此問題,並使您能夠在創建事件源時通過注冊消 息文件來充分利用事件日志的強大功能。這樣既可以支持本地化類別和消息,還減小了日志中消息的大小 。
注冊事件源的最新方法是使用 .NET Framework 2.0:
EventSourceCreationData source = new EventSourceCreationData("MyApp", "Application"); source.CategoryCount = 2; source.CategoryResourceFile = "path to dll"; source.MessageResourceFile = "path to dll"; source.ParameterResourceFile = "path to dll"; EventLog.CreateEventSource(source);
注意,盡管從技術上來講您可以保留三個資源 DLL,但 大多數應用程序都會將所有這三種資源類型(類別、消息和參數)納入單獨的一個 DLL 中。
使用 消息文件
為了為事件日志創建資源 DLL,您需要從創建一個或多個消息文件開始著手。為簡單起 見,我只使用一個消息文件,該文件為文本文件(文件擴展名通常是 .mc)。您可以使用名為 mc.exe 的 工具(消息編譯器)來處理這些文件。
消息編譯器會對幾個您可以隨時在消息文件中進行設置的 變量進行跟蹤。MessageId 是您希望針對每條消息進行更新的變量,而您可以針對消息文件的整個部分設 置一次 Severity 變量。如果您不顯式設置 MessageId,則所分配的 MessageId 可能會一個比一個大。 變量定義以空格(包括換行符)分離:
MessageId=1001 SymbolicName=AppStarted Severity=Informational
消息文本信息本身必須另起一行,並且根據需要可以占用任意行 數。直至出現帶有句號的新的一行為止,如下所示:
MessageId=1003 SymbolicName=DatabaseConnectionFailed Language=English Could not connect to SQL Server database %1. Reason: %2 . Language=Spanish No podía conectar con SQL Server database %1. Razón: %2 .
這裡我定義了一個消息,其中包含兩處已經本地化的文本信息。在編譯此消息文件之前,我需 要先定義所使用的語言。所以我在消息文件的頂部添加了一個帶有語言定義的標頭部分:
LanguageNames=(English=0x409:MSG00409)
LanguageNames=(Spanish=0xC0A:MSG00C0A)
它定義了我要用來表示每種語言及其區域設置 ID 的符號名稱。由於消息編譯器為每種語言單獨生成 一個輸出文件,因此我提供了每個文件應使用的名稱。以下是從命令行運行消息編譯器時得到的結果:
$ mc messages.mc
MC: Compiling messages.mc
$ dir
12/16/2006 05:18 AM 1,970 messages.h
12/16/2006 05:13 AM 1,199 Messages.mc
12/16/2006 05:18 AM 74 messages.rc
12/16/2006 05:18 AM 356 MSG00409.bin
12/16/2006 05:18 AM 352 MSG00C0A.bin
如您所見,消息編譯器會為每種語言生成一個 .bin 文件以及一個引用 .bin 文件的資源文件 (messages.rc)。messages.rc 文件如下所示:
LANGUAGE 0xa,0x3
1 11 MSG00C0A.bin
LANGUAGE 0x9,0x1
1 11 MSG00409.bin
現在,再進行兩步操作,您就可以構建一個資源 DLL 了:運行資源編譯器創建 .res 文件,然後使用 鏈接器將該文件鏈接到 DLL:
$ rc messages.rc
$ link /DLL /SUBSYSTEM:WINDOWS /NOENTRY /MACHINE:x86 messages.res
Microsoft (R) Incremental Linker Version 8.00.50727.42
Copyright (C) Microsoft Corporation. All rights reserved.
此時您應該會得到一個名為 messages.dll 的文件,您可以用它來創建事件源。
在 MSDN 文檔中查看有關 mc.exe 的內容,您可以從中了解很多有關消息文件本質的詳細內容,但這 裡我要指出幾處特別需要注意的地方。
首先,您的消息文件中應該包含已本地化的事件類型名稱。它們看上去與普通消息一樣,但不同之處 在於它們是短名稱,並且必須使用連續 MessageId(以數字 1 開始)。在創建事件源時,應將 CategoryCount 指定為最高的類型消息編號。以下是我的示例應用程序中的兩個類型定義(您可能會在自 己的應用程序中加入更有趣更豐富的類型):
;// Categories
MessageId=1 SymbolicName=Administrative
Language=English
Administrative
.
Language=Spanish
Administrativo
.
MessageId=2 SymbolicName=Operational
Language=English
Operational
.
Language=Spanish
Operacional
.
請記住,我們使用的消息編譯器最初是供 C 語言程序員編寫本機 Win32 應用程序的。您是否曾注意 過生成的 messages.h 文件?該文件包含一組供 C/C++ 程序員引用類型和消息使用的常量。這就是為什 麼 SymbolicName 變量會出現在我所有的消息和類型定義中的原因。您也許還注意到類型列表上方的奇怪 注釋:
;// Categories
從技術角度講,消息編譯器只需用分號來分隔注釋,但通常您會發現其後面跟有一個 C 樣式的注釋。 原因是編輯器還會將所有這些注釋發送到 messages.h 文件中。
如果 SymbolicName 變量僅用於創建 C 語言頭文件,那麼它對於使用 .NET 的程序員又有哪些好處呢 ?坦白地講,從目前來看,用處並不是很大。但是假設要編寫一個能夠將消息文件做為輸入並使用您選擇 的托管語言生成類定義的編譯器,那麼其意義就非常重大了。
盡管其他很多變量通常也要在消息文件頭中定義,但我還是選擇使用默認定義。以下為可獲得的默認 的消息嚴重級別,您可以在自己的消息定義中使用這些值:
SeverityNames=(Success=0
Informational=1
Warning=2
Error=3
)
消息文件在剛開始令人感到困惑的另外一點是參數的概念。參數可以分為兩類。最常見的類型是那些 需要填入運行時收集的數據的參數,例如 %1 和 %2 等。另一種類型是能夠從消息文件中分離出常見字符 串的參數。例如,在開發的早期,您可能並不希望在眾多條消息中對應用程序名稱進行硬編碼,那麼可以 將該名稱分離出來做為參數,然後再使用特定的語法 %%N,其中 N 代表參數資源 DLL 中替換字符串的 MessageId。
創建自定義日志
讓 IT 專業人士真正反感的是應用程序在事件日志中記錄了很多無關緊要的內容,以至於淹沒了正常 運轉的應用程序不時發出的真正重要的消息。您不應向應用程序日志寫入有關運行、分析或跟蹤/調試的 消息!這些消息最好還是放在自定義的應用程序日志中。您可以為希望創建的自定義日志指定一個名稱( 如果尚未存在自定義的日志),以在應用程序的安裝程序中創建一個自定義應用程序日志。以下代碼創建 了一個名為 MyApp 的新日志。請注意,您需要為所創建的每個日志文件提供一個不同的事件源名稱:
EventSourceCreationData source =
new EventSourceCreationData("MyApp Operational", "MyApp");
source.CategoryCount = 2;
source.CategoryResourceFile = "path to dll";
source.MessageResourceFile = "path to dll";
source.ParameterResourceFile = "path to dll";
EventLog.CreateEventSource(source);
在運行時記錄事件
正如您習慣於為應用程序創建數據層和簡化數據庫訪問的強類型參數一樣,您也應該考慮創建類來幫 助記錄事件。在示例應用程序中,我創建了兩個類來記錄事件。第一個類用於要寫入應用程序日志的管理 事件,第二個類用於要寫入應用程序自定義日志的操作事件。每個類針對每個事件使用一種方法,其強類 型參數代表每個事件的參數所需的數據。例如,以下是我的自定義操作日志中的事件代碼,該事件記錄了 一次數據庫連接失敗:
public void DatabaseConnectionFailed(
string dbName, string connectionString) {
operationalLog.WriteEvent(
new EventInstance(1003, 2, EventLogEntryType.Error),
dbName, connectionString, new StackTrace(1));
}
WriteEvent 方法是 .NET Framework 2.0 的新增方法。通過它,您可以傳遞要放入消息文件參數(例 如 %1)中的參數。圖 3 所示為日志中該事件的一個示例。
請注意,這一特殊的方法可以提供很多詳細信息,其中包括數據庫名稱、連接字符串和堆棧跟蹤。這 就是為何此消息會發送到我的自定義操作日志的原因。同時我還向應用程序日志發送了一條更簡單、更容 易操作的消息。
圖 3 參數化事件
總結
我們使用諸如事件日志的技術仔細檢測專為操作設計的應用程序。將 IT 專業人士視為應用程序的頭 等用戶,您將有助於確保系統穩定運行,並減少停機時間。通過創建自己的自定義事件日志並將日志中無 關緊要的事件集中於此,您可以在應用程序日志中為可操作事件開辟更多的空間,這些可操作事件會迅速 告知 IT 專業人士如何將您的應用程序恢復到正常的運行狀態。
有關現有事件系統的很多內容(包括 .mc 消息文件格式)都有些神秘。幸運的是,Windows Vista對 其內置的事件系統完全地進行了重新設計,這也將是即將推出的代號為“Longhorn”的 Windows Server 的一部分。雖然很多基本概念並未發生變化,但很多非常酷的新功能會使您更詳盡地記錄更豐富的事件。 此外,替換消息文件的新文件包含更多的事件元數據,這些數據存儲為 XML 格式,從而更便於分析。我 會在下一期專欄中對新系統進行介紹,敬請關注!
將您向 Keith 詢問的問題和提出的意見發送至:[email protected] [email protected].