◆イベントログ − VC++ 2005 その5 〜 イベントログバックアップ&クリア と モニタ

[ ホーム ] [ 上へ ]

<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ログに書込みがされる他のサービスに対しても通知されてコントロールに 表示されることにも注意してください。