向操做系統的事件管理器報告重大信息是一種很是有用的方式,特別是對於沒有界面的後臺服務而言。若是你對Windows編程有必定了解,應該很快就能想到使用ReportEvent這個API,而後快速寫出下面的程序: 編程
VOID TestReportEvent1() { // Get a handle to the event log. HANDLE h = RegisterEventSource(NULL, // Use local computer. _T(「EventLogDemo」)); // Event source name. if (h == NULL) { printf("Cannot register the event source."); return; } TCHAR szEventMsg[256] = _T("文件備份服務已啓動."); LPCTSTR lpInput[] = {szEventMsg} ; if (!ReportEvent(h, // Event log handle. EVENTLOG_INFORMATION_TYPE, // Event type. NULL, // Event category. 1001, // Event identifier. NULL, // No user security identifier. 1, // Number of substitution strings. 0, // No data. lpInput, // Pointer to strings. NULL)) // No data. { printf("Cannot report the event."); } else { printf("Report Event Successfully.\n"); } DeregisterEventSource(h); return; }
這個程序運行正常,到事件管理器中查看,確實多了一條記錄。 小程序
實際上咱們報告的內容只有紅框中的一個字符串佈局,可是在查看日誌時,日誌管理器會告訴咱們一堆東西,說事件描述未找到之類的,反正就是不太正常。數組
那正常的應該是什麼樣的呢?看下圖:ide
乾乾淨淨,清清爽爽,這纔是正確的結果。 佈局
那如何實現這個效果呢?須要分下面幾步來實現。 測試
1、編寫mc文件 編碼
mc文件是一個消息描述文件,它定義了消息的ID和消息的格式。不知道怎麼寫不要緊,參考MSDN中的例子,直接拷貝如下模板就好了。 spa
1 ; // ***** TestEventLog.mc ***** 2 3 ; // This is the header. 4 5 MessageIdTypedef=DWORD 6 7 SeverityNames=(Success=0x0:STATUS_SEVERITY_SUCCESS 8 Informational=0x1:STATUS_SEVERITY_INFORMATIONAL 9 Warning=0x2:STATUS_SEVERITY_WARNING 10 Error=0x3:STATUS_SEVERITY_ERROR 11 ) 12 13 14 FacilityNames=(System=0x0:FACILITY_SYSTEM 15 Runtime=0x2:FACILITY_RUNTIME 16 Stubs=0x3:FACILITY_STUBS 17 Io=0x4:FACILITY_IO_ERROR_CODE 18 ) 19 20 LanguageNames=(Chinese=0x804:MSG00804) 21 22 ; // The following are message definitions. 23 24 MessageId=1001 25 Severity=Informational 26 Facility=Runtime 27 SymbolicName=MSG_SERVICE_START 28 Language=Chinese 29 文件備份服務已啓動. 30 . 31 32 MessageId=1002 33 Severity=Informational 34 Facility=Runtime 35 SymbolicName=MSG_SERVICE_STOP 36 Language=Chinese 37 文件備份服務已中止. 38 .
上面的部分不須要改動,LanguageNames往下才是本身要定義的消息內容。注意消息的內容並非以換行結束的,必須在後面起一個新行寫上一個"."做爲結束符(第30、38行),不然會提示 EventLog.mc(linenum) : error : Unterminated message definition,根據錯誤提示的行號進行修改就能夠了。操作系統
一般來講,程序報告給事件管理器的事件都是預先定義好的事件,好比服務啓動了、中止了,或者出現了某錯誤等等。上面的mc文件中定義了兩個消息,MessageId就是未來在事件管理器中看到的事件ID,Serverity表示事件的嚴重程度,能夠從上面的SeverityNames中選一個,經常使用的是Informational表示成功或普通訊息,Warning爲警告信息,Error爲錯誤信息。SymbolicName即符號名稱,它由用戶根據本身的須要進行命名,好比服務啓動事件能夠命名爲MSG_SERVICE_START,在編寫代碼時會用到這個名字,具體怎麼用的,後面再講。 命令行
這時讀者可能會有一個疑問,就是這些消息內容都是固定的,可是實際運行過程當中會有一些變化的信息,好比本例做爲一個文件備份服務,想在備份成功時報告成功的是哪一個文件,備份失敗的時候報告操做失敗的文件名稱和錯誤代碼,這些變化的信息怎麼加入到消息內容中去呢?
事實上,事件消息的正文能夠是固定的一句話,也能夠是模板,其中須要替換的內容按順序用%n替換,n的取值範圍是1到99。
好比:將目標文件備份到 %1 時出錯,錯誤代碼爲 %2.
在使用這個模板時,只須要提供兩個變量就能夠了,事件系統會自動分別替換模板中的%1和%2。具體如何操做,後面會講到。
保存mc文件時可使用與操做系統相同的語言編碼,也可使用Unicode編碼。若是mc文件中使用了多種語言,那麼最好使用Unicode編碼保存該文件。
2、編譯mc文件
Visual Studio提供了一個程序mc.exe專門用於編譯mc文件。若是VS的可執行程序路徑已經添加到了PATH環境變量中,那麼直接在存放mc文件的目錄中打開cmd窗口,執行以下命令(本例中文件名爲EventLog.mc):
若是mc文件編寫沒有問題,那麼編譯經過時沒有其它的額外提示,不然會提示你錯誤信息及錯誤所在的行號,根據具體提示進行修改就能夠了。
若是mc文件的保存格式是Unicode的,這時的編譯參數要變成mc –u –U EventLog.mc
第一個-u表示輸入文件是Unicode編碼,第二個-U表示輸出文件要使用Unicode編碼,若是軟件支持多國語言,那最好是使用Unicode編碼。
編譯成功後,會生成至少三個文件,分別是一個h文件,一個rc文件,一個MSG開頭的bin文件,具體文件名與mc文件中定義的LanguageNames有關,好比中文就是MSG00804.bin。
接下來要作的,就是把這個rc文件編譯到程序中去,它將成爲資源的一部分。一般有兩種作法,一種是編譯爲純資源dll,一種是編譯到exe自己,選擇哪一種純屬我的愛好。若是你的程序原本就是由多個模塊組成的,那麼就編譯成dll好一些,若是你只有一個exe,不想額外攜帶dll,那就編譯到exe自己。
編譯爲純資源dll的方法仍然是使用命令行:
先編譯rc文件,最後使用Link命令編譯爲純資源dll。
若是要編譯到exe,那麼在exe自己的rc文件中添加一行:
#include "EventLog.rc"
就能夠了。
EventLog.rc的內容其實很簡單,以下:
LANGUAGE 0x4,0x2 1 11 MSG00804.bin
實際是一個類型爲11,名稱爲1的資源自定義資源文件,文件指向MSG00804.bin。
因此,若是使用包含方式有問題的話,能夠直接把上面兩行內容添加到exe自己的rc文件中。
若是把mc文件加入到工程中的話,須要設置自定義編譯操做(注意選擇"全部設置",就不須要每一個編譯配置都從新設置一次了,除非你須要不一樣的配置選項)。
VC6設置以下(原諒我還在用VC6寫一些小程序):
VS2010的設置以下:
小提示:
因爲每次mc文件編譯後會從新生成這幾個文件,若是把這幾個文件添加到了工程裏就會致使IDE重複提示"文件已修改,是否從新加載"的狀況。因此,只添加mc文件到工程中,但不要添加生成的h文件和rc文件。h文件只是不添加到工程中,包含使用是沒有問題的。
3、註冊事件源
這個按MSDN中的操做來就能夠了。
具體來講就是:
在KEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog下面添加相應的鍵值。這裏有幾個大類,主要的是"Application","Security"和"System",分別對應於系統的三個日誌頻道。一般,咱們本身的應用程序報告的事件寫在Application頻道下,固然也能夠定義本身的頻道。
好比,測試程序名爲TestEventLog,那麼就註冊到Application\TestEventLog下面,具體來講就是添加幾個鍵值,以下:
具體代碼能夠直接抄MSDN:
BOOL AddEventSource(LPCTSTR lpszChannelName, LPCTSTR lpszSourceName , LPCTSTR lpModulePath) { BOOL bResult = FALSE ; DWORD dwCategoryNum = 1; HKEY hk; DWORD dwData, dwDisp; TCHAR szBuf[MAX_PATH] = {0}; size_t cchSize = MAX_PATH; __try { // Create the event source as a subkey of the log. _stprintf(szBuf, _T("SYSTEM\\CurrentControlSet\\Services\\EventLog\\%s\\%s"), lpszChannelName, lpszSourceName); if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, szBuf, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hk, &dwDisp)) { printf("Could not create the registry key.\n"); __leave; } // Set the name of the message file. if (RegSetValueEx(hk, // subkey handle _T("EventMessageFile"), // value name 0, // must be zero REG_EXPAND_SZ, // value type (LPBYTE) lpModulePath, // pointer to value data (DWORD) (lstrlen(lpModulePath)+1)*sizeof(TCHAR))) // data size { printf("Could not set the event message file.\n"); __leave; } // Set the supported event types. dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE; if (RegSetValueEx(hk, // subkey handle _T("TypesSupported"), // value name 0, // must be zero REG_DWORD, // value type (LPBYTE) &dwData, // pointer to value data sizeof(DWORD))) // length of value data { printf("Could not set the supported types.\n"); __leave; } // Set the category message file and number of categories. if (RegSetValueEx(hk, // subkey handle _T("CategoryMessageFile"), // value name 0, // must be zero REG_EXPAND_SZ, // value type (LPBYTE) lpModulePath, // pointer to value data (DWORD) (lstrlen(lpModulePath)+1)*sizeof(TCHAR))) // data size { printf("Could not set the category message file.\n"); __leave; } if (RegSetValueEx(hk, // subkey handle _T("CategoryCount"), // value name 0, // must be zero REG_DWORD, // value type (LPBYTE) &dwCategoryNum, // pointer to value data sizeof(DWORD))) // length of value data { printf("Could not set the category count.\n"); __leave; } bResult = TRUE; } __finally { if (hk != NULL) { RegCloseKey(hk); } } return bResult; }
而後調用:
AddEventSource(_T("Application"),_T("EventLogDemo"),szModulePath);
szModulePath就是包含了消息定義資源的dll或者exe的路徑。
這樣,事件源就註冊好了。
4、如何報告日誌
到這一步,纔算作好了全部的準備工做。
報告事件仍然使用如下三個API:
RegisterEventSource獲得事件管理器的句柄
ReportEvent進行報告
DeregisterEventSource關閉事件源句柄
注意再來看一下ReportEvent的參數:
BOOL ReportEvent( __in HANDLE hEventLog, //事件源句柄 __in WORD wType, //消息類型:信息、警告、錯誤 __in WORD wCategory, __in DWORD dwEventID,//事件ID __in PSID lpUserSid, __in WORD wNumStrings, //要插入到模板中的字符串數量 __in DWORD dwDataSize, __in LPCTSTR* lpStrings, //變量字符串組 __in LPVOID lpRawData );
其中wNumStrings就是模板中要插入的變量字符串數量,lpStrings就是變量字符串數組,在顯示日誌內容時,事件查看器會使用數組中的內容依次替換掉事件字符串模板中的%一、%2這些變量;而dwEventID就是前面在mc文件中定義的MessageId,要使用它,須要包含mc文件編譯生成的那個頭文件。它的部份內容以下:
除了模板以外,還能夠有附加數據,以二進制形式提供,須要使用到dwDataSize和lpRawData兩個參數,相信對於這兩個參數如何使用應該不會陌生。
因爲每次報告事件都須要獲得事件源的句柄,因此能夠把這一過程簡單包裝下:
BOOL ReportEventDebugMsg(WORD EventType,DWORD dwEventId,WORD cInserts,LPCTSTR *lpStrings) { BOOL bResult = FALSE ; HANDLE h = RegisterEventSource(NULL, // Use local computer. g_szEventSourceName); // Event source name. if (h == NULL) { printf("Cannot register the event source."); return FALSE; } SetLastError(ERROR_SUCCESS); bResult = ReportEvent(h,EventType,NULL,dwEventId,NULL,cInserts,0,lpStrings,NULL); printf("Report Event %s , Errcode = %d .\n",bResult?"Success":"Failed",GetLastError()); DeregisterEventSource(h); return bResult; }
接下來能夠這麼調用(根據前面的定義,共報告服務啓動、操做成功、操做失敗、服務中止四個事件):
//正確的報告方式 VOID TestReportEvent3() { //Report Service Start ReportEventDebugMsg(EVENTLOG_INFORMATION_TYPE,MSG_SERVICE_START,0,NULL); //使用模板報告特定事件 LPCTSTR lpInputStrings[2] = {0}; TCHAR szFilePath[MAX_PATH] = _T("D:\\test.dat"); TCHAR szErrCode[32] = _T("32"); //Report something Success lpInputStrings[0] = szFilePath; ReportEventDebugMsg(EVENTLOG_INFORMATION_TYPE,MSG_BACKUP_DONE,1,lpInputStrings); //Report something Error lpInputStrings[0] = szFilePath; lpInputStrings[1] = szErrCode; ReportEventDebugMsg(EVENTLOG_ERROR_TYPE,MSG_BACKUP_FAIL,2,lpInputStrings); //Report Service Stop ReportEventDebugMsg(EVENTLOG_INFORMATION_TYPE,MSG_SERVICE_STOP,0,NULL); printf("Report Finished.\n"); }
最後來看一下效果:
共產生了4個事件。
再看一下其中含有變量的兩個事件:
達到了預期效果,這纔是完整的、正確的ReportEvent的使用方式。