カスタムログ

EventReportをカスタムログ対応させるための実装作業を記録します。

(6/16記)このドキュメントの完成とともに,プログラムの実装も完了しました。 近日中にVectorで新バージョンとして公開したいと思います。

Contents

カスタムログとは?

イベントログ

「イベントログ」は,WindiwsNTから新たに導入されたWindowsの標準的なログ機能です。 NTで実装された当初は,次の3つのログエントリが用意されていました。

アプリケーションログ
一般アプリケーションのログを記録する
システムログ
主にWindowsシステムプロセスのログを記録する
セキュリティ
ログイン認証などセキュリティに関するログを記録する

NTでは,例えばアプリケーションのログはすべて同じエントリに記録されるようになっていて, 特定のアプリケーションのログを参照するためには,ログの「ソース」によるフィルタを使用して いました。

カスタムログの導入

以上のようなイベントログに対して,Windows2000 より新たに「カスタムログ」が導入されました。 Microsoft の発表しているドキュメントでは,カスタムログは次のように定義されています。

カスタム ログ エントリとは、パッケージ、すべてのコンテナ、およびタスクに使用できる一連の 標準的なログ記録イベントとは異なるエントリのセットです。カスタム ログ エントリは、パッケージ内 の特定のタスクに関する有益な情報を取得するように調整されます。

WindowsNTのサポートが終了したこととも関係があるのでしょうが,最近になってMicrosoft製品にも カスタムログを使用するものが見られるようになりました。例えばInternet Explorerでも,最新の 7.0 からはカスタムログを用いた記録を行っているようです。

Microsoft製品で使用され始めたことから,今後開発されるソフトウェアではカスタムログの使用が 一般的になるものと思われます。

カスタムログ対応

次の章では,EventReportでのカスタムログ実装作業について説明します。

カスタムログの読み込み

イベントログを読み込むためには,最初にログをオープンしなければなりません。 Win32API の範疇では,"OpenEventLog" を使用してこれを行います。

HANDLE OpenEventLog(
  LPCTSTR lpUNCServerName,  // サーバー名へのポインタ
  LPCTSTR lpSourceName      // ログファイルの名前へのポインタ
);

ここでログファイル名として,従来は"Application""System""Security"のいずれかを指定する ようになっていました。Win2000以降ではここにカスタムログ名を指定することで,任意のイベント ログをオープンできるようになっています。

カスタムログ名の取得

OpenEventLog でカスタムログ名を指定するためには,前もってカスタムログ名の一覧を 取得しておく必要があります。

カスタムログを含めたイベントログ名称の一覧を取得するAPIは存在しないようですが, 以下のレジストリを参照することで情報を得ることができます。

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog

この下にあるキー名がイベントログ名になります。 ちなみにカスタムログを作成するAPIも存在しないのですが,同じレジストリ下にキーを 作成することでカスタムログを作成可能です。

ログ種類の保持

現在のEventReportでは,ログ種別として「アプリケーション」「システム」「セキュリティ」 の3種類のみを想定していました。このため,フィルタ定義でログ種別を保持するデータとして, 以下のような列挙体を使用しています。

--- EvRefInf:EvInfFilter.h ---

  //
  //  選択されているイベント種類を保持します。
  //
  enum enEventSect{
    EVENT_SYSTEM = 0,
    EVENT_SECURITY,
    EVENT_APPLICATION
  };
  enEventSect eventSect_;

カスタムログに対応するためには,ログ種類は任意の文字列で指定できる必要がありますので, イベント種類は文字列として保持することにしました。従来のイベントログ種別については, APIで使用する"System"・"Application"・"Security"という文字列で表現します。 これらの文字列は,定数として同じ場所で宣言しておくことにしました。

--- EvRefInf:EvInfFilter.h ---

  //
  //  選択されているイベント種類を保持します。
  //      * カスタムログ対応
  //
  tstring eventSect_;
  static const tstring EVENT_SYSTEM;      // System
  static const tstring EVENT_SECURITY;    // Security
  static const tstring EVENT_APPLICATION; // Application

--- EvRefInf:EvInfFilter.cpp ---

  const tstring CFilterItem::EVENT_SYSTEM = "System";
  const tstring CFilterItem::EVENT_SECURITY = "Security";
  const tstring CFilterItem::EVENT_APPLICATION = "Application";

EventReportでは,フィルタを含む設定内容をレジストリに保存しています。 今回変更したログ種別については,設定された文字列をそのままの内容で保存することにします。 但し,これまで設定された内容も読み込みできるように,数値(0,1,2)が記録されている場合は 文字列に読み替えする処理を実装します。

--- EvRefInf:EvInfFilter.cpp ---

bool
CFilterItem::Save(HKEY hkey) const {
  CRegistry reg;
  if(!reg.Create(hkey, name_.c_str()))
    return false;
//reg.PutValue(_T("EventSection"), (DWORD)eventSect_);
  reg.PutValue(_T("EventSection"), eventSect_);
  reg.PutValue(_T("EventTypeInformation"), typeInfo_);
  reg.PutValue(_T("EventTypeWarning"), typeWarn_);
  reg.PutValue(_T("EventTypeError"), typeError_);
  reg.PutValue(_T("EventTypeAuditSuccess"), typeAuditSuccess_);
  reg.PutValue(_T("EventTypeAuditFailure"), typeAuditFailure_);
  reg.PutValue(_T("EventSourceFlag"), filterSource_);
  reg.PutValue(_T("EventSource"), filterSource_ ? valSource_ : _T(""));
  reg.PutValue(_T("EventIdFlag"), filterEventId_);
  reg.PutValue(_T("EventId"), filterEventId_ ? valEventId_ : 0);
  reg.Close();
  return true;
}
--- EvRefInf:EvInfFilter.cpp ---

void
CFilterItem::Load(const tstring& name, HKEY hkey){
  setDefault();
  name_ = name;

  CRegistry reg;
  if(reg.Open(hkey, name.c_str())){
    DWORD dwSect;
    if(reg.GetValue(_T("EventSection"), dwSect)) {
      switch((int)dwSect) {
      case 0:  eventSect_ = EVENT_SYSTEM;  break;
      case 1: eventSect_ = EVENT_SECURITY; break;
      case 2: eventSect_ = EVENT_APPLICATION; break;
      }
    } else {
      reg.GetValue(_T("EventSection"), eventSect_);
    }
    reg.GetValue(_T("EventTypeInformation"), typeInfo_);
    reg.GetValue(_T("EventTypeWarning"), typeWarn_);
    .....................................

カスタムログ名の取得

カスタムイベントログ指定を保持するデータクラスを修正しましたので,今度は 入力部分を修正します。現在のコントロールパネル入力ダイアログは「アプリケーション」「システム」 「セキュリティ」のいずれかを選択する,ラジオボタン形式となっています。

カスタムイベントログを選択するため,このコントロールをリストボックスに変更します。 リストボックスの内容は,前述したログエントリ一覧を文字列昇順に格納します。但し標準エントリで ある「アプリケーション」「システム」「セキュリティ」の3つについては, イベントビューアの表記に合わせてこの順,かつ日本語文字列で先頭に表示することにしました。

ログエントリ一覧を取得するメソッドでは,最初に規定の3エントリを設定し,その後ろに レジストリから取得したエントリ名称を追加しています。

--- EvRepCpl:EvRepDlgP3.cpp ---

int
CEvRepDlgP3::getLogEntryNames(tstringArray& entries) {
  entries.clear();
  entries.push_back(CFilterItem::EVENT_APPLICATION);
  entries.push_back(CFilterItem::EVENT_SECURITY);
  entries.push_back(CFilterItem::EVENT_SYSTEM);
  int resultCount = 3;

  CRegistry reg;
  if(reg.Open(hkeyLogEntry, regPathLogEntry)) {
    tstringArray regLogEntries;
    if(reg.EnumSubKey(regLogEntries)) {
      for(tstringArray::iterator it = regLogEntries.begin();
          it != regLogEntries.end(); ++it) {
        if(std::find(entries.begin(), entries.end(), *it) == entries.end()) {
          entries.push_back(*it);
          ++resultCount;
        }
      }
    }
    reg.Close();
  }
  return resultCount;
}

サービスの変更

コントロールパネルで設定された情報を参照するサービス側処理については, 前述のようにAPIが変更されていないこともあってほとんど変更はありません。 唯一,これまでは整数値で保持していたエントリタイプ情報が文字列になったため, 変換処理が不要になったことだけが修正点です。

--- EvRepSvc:EventSelector.cpp ---

LPCTSTR
CFilterSelector::category(void) const {
  return item_->eventSect_.c_str();
  //switch((int)item_->eventSect_) {
  //case CFilterItem::EVENT_SYSTEM:
  //  return _T("System");
  //case CFilterItem::EVENT_SECURITY:
  //  return _T("Security");
  //default:
  //  return _T("Application");
  //}
}