<3.イベントログのバックアップ & クリア>
イベントログのバックアップとクリアに関してはイベントログ名をOpenEventLogに渡して、 バックアップファイル名をBackupEventLogまたはClearEventLogに指定すればいいだけで、 ほとんど書くこともないので例を掲載するだけにします。
前頁のイベントログの読込みでつかった TestEventLogプロジェクトに加筆・修正します。
// TestEventLog.cpp : コンソールアプリケーションのエントリポイントを定義します。 // #include "stdafx.h" #include <time.h> // ・ // ・ // ・ //バックアップ& クリア-------------------------------------------------------------------- BOOL BakClrEvtlog(LPCTSTR lpszLog, //イベントログバックアップ & クリア LPCTSTR lpszBckFile, BOOL bClear = FALSE); int _tmain(int argc, _TCHAR* argv[]) { return ! BakClrEvtlog(_T("Application"), _T("d:\\temp\\applog_bak.evt"), FALSE); //ListAppEventlog(argc <= 1 ? NULL : argv[1]); //return ::_tsystem(_T("pause")); //return ! CreateEventSource(_T("MyApp"), _T("%SystemRoot%\\System32\\MyApp.dll"), 2); } //イベントログバックアップ & クリア //引数 lpszLog ログファイル名(レジストリのイベントログキーのPrimaryModuleの名前) // lpszBckFile バックアップをするファイル名 // bClearがTRUEの時、ファイル名が指定されてあればバックアップ後にクリア // NULLが指定されていたらバックアップなしでクリア // bClearがFALSEの時、ファイル名が指定されていないと関数が失敗 // bClear FALSEの場合はlpszLogで指定したログファイルをバックアップ // TRUEの場合はlpszLogで指定したログファイルをクリア // (lpszBckFileにファイル名が指定されていたらバックアップ後クリア) BOOL BakClrEvtlog(LPCTSTR lpszLog, LPCTSTR lpszBckFile, BOOL bClear /*=FALSE*/) { typedef BOOL (__stdcall *LPFNBKCLREVTLOG)(HANDLE, LPCTSTR); BOOL bRet; LPFNBKCLREVTLOG pfnEvt = bClear ? ::ClearEventLog : ::BackupEventLog; HANDLE hEvent = ::OpenEventLog(NULL, lpszLog); if (! hEvent) return FALSE; bRet = pfnEvt(hEvent, lpszBckFile); ::CloseEventLog(hEvent); return bRet; } |
上記はイベントログのバックアップ例です。ビルド後、実行すると D:\temp\applog_bak.evtにバックアップができているのでイベントビューワで確認してください。 イベントログをクリアしたいときは、BakClrEvtlogの第三引数にTRUEを指定してください。
<4.イベントログのモニタリング>
イベントログへの書込みはWindowsや各アプリケーションがReportEventによって
行っていますが、イベントビューワを実行してはじめてログがとられていることがわかります。
つまり、通常ではいつイベントログへの書込みが行われているかは分かりません。ですが、
NotifyChangeEventLogというAPIを使うとイベントログへの書込みを通知してくれるようになります。
その定義は次のようです。
BOOL NotifyChangeEventLog( HANDLE hEventLog, // イベントログのハンドル HANDLE hEvent // イベントオブジェクトのハンドル );
hEventLogはOpenEventLogなどで開いたイベントログへのハンドルを指定します。 今回はMyAppイベントソースの書込み先であるApplicationログを指定します。
hEventはCreateEventで作成された同期オブジェクトのハンドル、 MFCの場合はCEventのm_hObjectの値を指定します。
イベントログへの書込みが通知される仕組みは次のとおりです。
hEventLogで指定された対象のイベントログへの書込みが行われると、hEventで指定したイベント
(同期オブジェクト)にPulseEventが自動的に発行されてシグナル状態になります。つまり、
イベント(同期オブジェクト)の非シグナル状態によって待機していたスレッドがPulseEventで
解放されて以降のコードが実行されます。この以降のコードでメッセージボックスを
表示したりするなど任意の処理を行えば、イベントログへの書込みを認識できることになります。
イベントログへの書込みの通知を受けるには上記のようにスレッドが非シグナル状態で待機してない といけないので、メインスレッドでは扱いづらいので、通知を受取るための別スレッドを 作成する必要があります。
では、イベントログへの書込みの際に作成した FileExistプロジェクトに通知機能を追加してみます。 イベントログへの通知を受取ったら、IDC_STC_NOTIFYEVTのスタティックコントロールに書き込み時刻、 イベントソース名とイベントIDを表示します。他の情報も表示可能ですので、興味のある方は イベントログの読込みを参照してください。
記述内容はダイアログのCFileExistDlgクラスのみでヘッダ・実装ファイルに 以下のように加筆・修正してください。
(FileExistDlg.h)
// FileExistDlg.h : ヘッダーファイル // #pragma once #include "afxmt.h" // CFileExistDlg ダイアログ> class CFileExistDlg : public CDialog { // ・ // ・ // ・ protected: CWinThread *m_pThread; //セカンダリスレッドへのポインタ CEvent m_EventEvtlog; //イベントログ通知用同期オブジェクト CEvent m_EventTerminate; //アプリ終了用同期オブジェクト HANDLE m_hEvent; //イベントログへのハンドル protected: afx_msg LRESULT OnNotifyEvtlog(WPARAM wParam, LPARAM lParam); //イベントログ書込み通知 BOOL GetLastEventLogRecord(HANDLE hEventlog, EVENTLOGRECORD **lppResult); //最新のイベント項目取得 public: static UINT __cdecl NotifyEvtProc(LPVOID pParam); //ワーカスレッド制御関数 protected: DECLARE_MESSAGE_MAP() }; |
(FileExistDlg.cpp)
// FileExistDlg.cpp : 実装ファイル // #include "stdafx.h" #include "FileExist.h" #include "FileExistDlg.h" #include "..\\MyApp\\MyAppMsg.h" #define EVENT_SRC_NAME "MyApp" //イベントソース名 #define NOTIFYEVTNAME "NotifyEvt" //イベントログ用イベント名 #define TERMINATEEVTNAME "TerminateEvt" //セカンダリスレッド終了用イベント名 #define WM_NOTIFYEVTLOG (WM_USER + 5) //イベントログ書込み通知用メッセージ // CFileExistDlg ダイアログ CFileExistDlg::CFileExistDlg(CWnd* pParent /*=NULL*/) : CDialog(CFileExistDlg::IDD, pParent) , m_pThread(NULL) , m_EventEvtlog( FALSE, TRUE, _T(NOTIFYEVTNAME) ) , m_EventTerminate( FALSE, FALSE, _T(TERMINATEEVTNAME) ) , m_hEvent(0) { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); } // ・ // ・ // ・ BEGIN_MESSAGE_MAP(CFileExistDlg, CDialog) ON_WM_PAINT() ON_WM_QUERYDRAGICON() //}}AFX_MSG_MAP ON_BN_CLICKED(IDC_BTN_EXEC, &CFileExistDlg::OnBnClickedBtnExec) ON_MESSAGE(WM_NOTIFYEVTLOG, &CFileExistDlg::OnNotifyEvtlog) END_MESSAGE_MAP() // CFileExistDlg メッセージハンドラ BOOL CFileExistDlg::OnInitDialog() { CDialog::OnInitDialog(); // ・ // ・ // ・ //セカンダリスレッド開始 m_pThread = AfxBeginThread(NotifyEvtProc, this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED, NULL); m_pThread->m_bAutoDelete = FALSE; m_pThread->ResumeThread(); //イベントログ通知機能開始 m_hEvent = ::OpenEventLog(NULL, _T("Application")); if (! m_hEvent) TRACE( _T("Can't Open Application Eventlog\n") ); if (! ::NotifyChangeEventLog(m_hEvent, (HANDLE)m_EventEvtlog) ) TRACE( _T("Can't Notify that Service Wrote it in the Eventlog\n") ); //イベントログへの書込み if (! WriteEvent(EVENTLOG_INFORMATION_TYPE, APP_ACTION, APP_OPEN, 0, 0, NULL, NULL) ) TRACE(_T("Can't Write it in the Eventlog\n")); return TRUE; } // ・ // ・ // ・ //検索ボタンクリック void CFileExistDlg::OnBnClickedBtnExec() { // ・ // ・ // ・ } //OnOKオーバーライド void CFileExistDlg::OnOK() { PreEndDialog(); CDialog::OnOK(); } //OnCancelオーバーライド void CFileExistDlg::OnCancel() { PreEndDialog(); CDialog::OnCancel(); } //ダイアログ終了前処理(ダイアログ終了をイベントログへ書込む) void CFileExistDlg::PreEndDialog() { //イベント書込み if (! WriteEvent(EVENTLOG_INFORMATION_TYPE, APP_ACTION, APP_CLOSE, 0, 0, NULL, NULL) ) TRACE( _T("Can't Write it in the Eventlog\n") ); //イベントログ書込み通知終了 if (m_hEvent) { ::CloseEventLog(m_hEvent); m_hEvent = 0; } //セカンダリスレッド終了 m_EventTerminate.SetEvent(); ::WaitForSingleObject(m_pThread->m_hThread, INFINITE); delete m_pThread; m_pThread = NULL; } //セキュリティ識別子(SID)取得 BOOL CFileExistDlg::GetSID(PSID *ppSid) { // ・ // ・ } //イベントログへの書込み BOOL CFileExistDlg::WriteEvent(WORD wType, WORD wCategory, DWORD dwEventID, WORD wNumStrings, DWORD dwDataSize, LPCTSTR *lpStrings, LPVOID lpRawData) { // ・ // ・ } //最新のイベントログレコード取得 //引数 hEventlog イベントログのハンドル(OpenEventLogで取得) // lppResult 一番最後にログに記録されたイベントへのポインタのアドレス(出力) //戻値 成功 lpResultに結果を受け取る 失敗lpResultはNULLに設定される //仕様 lpResultはこの関数で領域確保されるので使い終わったらdelete [] lpResultを実行する事 BOOL CFileExistDlg::GetLastEventLogRecord(HANDLE hEventlog, EVENTLOGRECORD **lppResult) { ASSERT(hEventlog); DWORD dwOldestRec = 0; //最古のイベントログレコード番号 DWORD dwNumRec = 0; //イベントログレコード数 BYTE cDummy; BYTE *pBuf = NULL; //最新のイベントログ項目を受取るEVENTLOGRECORDへのポインタ DWORD dwRead = 0; //ReadEventLogで結果を受取ったバイト数 DWORD dwMinNeed = 0; //次のイベントログ項目に必要なバイト数 //最古のイベントレコード番号取得 if (! ::GetOldestEventLogRecord(hEventlog, &dwOldestRec) ) return FALSE; //イベントログのレコード数取得 if (! ::GetNumberOfEventLogRecords(hEventlog, &dwNumRec) ) return FALSE; //最初(最新)のイベントログレコードのサイズをdwMinNeedに取得 if (::ReadEventLog(hEventlog, EVENTLOG_SEEK_READ | EVENTLOG_BACKWARDS_READ, dwOldestRec + dwNumRec - 1, &cDummy, 0, &dwRead, &dwMinNeed) || ::GetLastError() != ERROR_INSUFFICIENT_BUFFER ) return FALSE; //最新イベントログ項目受取り用のEVENTLOGRECORDの領域確保 pBuf = new BYTE[dwMinNeed]; //最初(最新)のイベントログレコードをpBufに取得 if (! ::ReadEventLog(hEventlog, EVENTLOG_SEEK_READ | EVENTLOG_BACKWARDS_READ, dwOldestRec + dwNumRec - 1, pBuf, dwMinNeed, &dwRead, &dwMinNeed) ) { delete [] pBuf; pBuf = NULL; } if (pBuf) //成功> *lppResult = (EVENTLOGRECORD *)(pBuf); else //失敗 *lppResult = NULL; return *lppResult ? TRUE : FALSE; } //イベントログへの書込み通知ユーザ定義メッセージハンドラ LRESULT CFileExistDlg::OnNotifyEvtlog(WPARAM wParam, LPARAM lParam) { EVENTLOGRECORD *pEvtRec = NULL; CString strResult; //最新のイベントログ項目を取得 if (! GetLastEventLogRecord(m_hEvent, &pEvtRec) ) { strResult = _T("最新のイベントログ項目を取得できません"); return 1; } CTime t = CTime(pEvtRec->TimeGenerated); LPCTSTR lpszSrc = (LPCTSTR)( (PBYTE)pEvtRec + sizeof(EVENTLOGRECORD) ); //イベントログ項目文字列作成 strResult.Format( _T("日時:%d/%d/%d %02d:%02d:%02d イベントソース:%s イベントID:0x%X が書込まれました"), t.GetYear(), t.GetMonth(), t.GetDay(), t.GetHour(), t.GetMinute(), t.GetSecond(), lpszSrc, pEvtRec->EventID); //スタティックコントロールに書き込まれたイベントの表示 GetDlgItem(IDC_STC_NOTIFYEVT)->SetWindowText(strResult); if (pEvtRec) {delete [] pEvtRec; pEvtRec = NULL;} return 0; } //ワーカスレッド制御関数(イベントログへの書込み通知用) UINT CFileExistDlg::NotifyEvtProc(LPVOID pParam) { CFileExistDlg *pDlg = reinterpret_cast<CFileExistDlg *>(pParam); CSyncObject *pSync[] = {& pDlg->m_EventEvtlog, & pDlg->m_EventTerminate}; CMultiLock multilock(pSync, sizeof pSync / sizeof pSync[0], FALSE); //イベントログへの書込み or ダイアログ終了待機 while ( multilock.Lock(INFINITE, FALSE, 0) == WAIT_OBJECT_0 ) //(A) { ::PostMessage(pDlg->m_hWnd, WM_NOTIFYEVTLOG, 0, 0); } AfxEndThread(0, FALSE); return 0; } |
簡単に解説しますが、前述したように別スレッドを作っています。 つまりマルチスレッドなプログラムで、これはこれでなかなか難しいお題なので こちらも理解される必要があるかと思います。 これは分かっている前提で書きます。
コンストラクタでイベントログ書込みの通知を受け取るための同期オブジェクト m_EventEvtlogとセカンダリスレッドを終了させるための同期オブジェクトm_EventTerminateを 構築します。(m_EventEvtlogは手動イベント)
CFileExist::OnInitDialog内で AfxBeginThreadでセカンダリスレッドを作成して 制御関数にNotifyEvtProcを指定します。(セカンダリスレッドでこの関数が実行されます)。その後 OpenEventLogで「Application」ログのハンドルを取得して、NotifyChangeEventLogに コンストラクタで作成したCEvent型のm_EvetnEvtlogを指定して実行します。これでApplication イベントログに書込みが発生したら、m_EventEvtlogがシグナル状態になるようにできました。
一方、先ほどセカンダリスレッド側で実行されているNotifyEvtProcですが、 各CEvent構築時にbInitialOwn引数をFALSEにしているので(A)の段階でPulseEventが発行されるまで、 つまり、イベントログに書込みが発生するまで待機状態になっています。この後、 このアプリケーション内の場合、WriteEventが実行されてイベントログへの書込みが行われたら、 m_EventEvtlogがシグナル状態になり、PostMessageが実行されて、プライマリスレッドの メッセージハンドラCFileExist::OnNotifyEvtlogが呼ばれます。
ここで、GetLastEventLogRecordを呼び出し、最新の(今書き込まれた)イベントの 項目をEVERNTLOGRECORDへのポインタという形で取得します。なお、この最新のイベントログの 項目を含めた任意のイベントログを取得するには、GetOldestEventLogRecordと GetNumberOfEventLogRecordというAPIを使って、最古のイベントレコード番号とイベント項目の 総数を取得してReadEventLogで検索することになります。
最後にイベントの必要な情報をIDC_STC_NOTIFYEVTコントロールに表示しています。
イベントログの書込みの通知が必要なくなれば、PreEndDialogで書かれているように オープンしているイベントログのハンドルを閉じればいいです。
解説はこのくらいにして、プロジェクトをビルドして実行すると次のように イベントログへの書込みがIDC_STC_NOTIFYEVTコントロールに表示されます。このアプリケーションの 起動・終了と検索でMyAppイベントソースとしてログに書込みが実行されると通知が行われるだけでなく、 Applicationログに書込みがされる他のサービスに対しても通知されてコントロールに 表示されることにも注意してください。