Запись сообщений в журнал событий Windows на Delphi

=== Запись сообщений в журнал событий Windows на Delphi ===
Приложение может записывать сообщения в журнал используя следующие функции WinAPI. Подробное описание параметров этих функций содержится в документации к API. 
* RegisterEventSource - Открывает handle для доступа к журналу на локальной или удаленной машине. 
* ReportEvent - Собственно записывает сообщение.
Для записи сообщений в журнал в упрощенной манере просто произведите вызов RegisterEventSource с именем машины (UNC), в журнал которой вы хотите поместить сообщение (nil для локальной машины), и именем события. Имя события это обычно имя приложения, но может быть чем-то более информативным. Как только источник событий зарегистрирован, можно записывать события при помощи ReportEvent с handle, который вернула RegisterEventSource. 
Пример: 
var
 EventLog: THandle;
 MyMsg: Array[0..2] of PChar;
EventLog := RegisterEventSource(nil, PChar('MyApplication'));
MyMsg[0] := 'A test event message';
ReportEvent(EventLog, EVENTLOG_INFORMATION_TYPE, 0, 0, nil, 1, 0, @MyMsg, nil);
Однако текст сообщения, записанного в журнал будет предварен текстом:
 "The description for Event ID (0) in Source (MyApplication) cannot be found. The local computer may not have necessary registry information or message DLL files to display messages from a remote computer. The following information is part of the event:"
 
 "Не найдено описание для события с кодом (0) в источнике (MyApplication). Возможно, на локальном компьютере нет нужных данных в реестре или файлов DLL сообщений для отображения сообщений удаленного компьютера. В записи события содержится следующая информация :)
 
 Замечание: Это сообщение специфично для Windows2000 и может немного отличаться на других версиях.
Для предотвращения появления этого текста необходимо внести в реестр некоторые ключи, как показано ниже, и определить строковые ресурсы (это может быть выполнено любым компонентом вашего приложения, не обязательно приложением, которое будет записывать события). Соответствующие записи реестра описаны ниже. Примеры кода предполагают, что строковые ресурсы и категории расположены в том же исполняемом файле, который содержит программу, записывающую события. Ключи категорий являются опциональными. Смысл этих ключей реестра и строковых ресурсов в том, что журнал событий использует строку, а приложение записывает в журнал в виде форматированного аргумента, и журналу необходимо знать, где находится описатель формата для этой строки. Кроме того, в журнале может храниться информация о категории события, полезная для просмотра событий. Это удобнее, чем просто отображать множество однотипный событий "Нет". Самый простой определитель формата это %1, который просто передаст в журнал входную строку. Для более подробного изучения определителей формата см. API документацию для FormatMessage. 
==== Ключи реестра ====
Создайте следующий ключ реестра: 
 HKEY_LOCAL_MACHINESYSTEM - CurrentControlSet - Services - Eventlog - Application - <AppName> 
Имя приложения AppName должно совпадать с именем источника, использованного при вызове RegisterEventSource, потому что просмотрщик событий будет использовать это имя для отыскивания событий. 
Создайте следующие ключи: 
Имя ключа 	Тип 	Описание
CategoryCount (Optional) 	Integer	Количество категорий событий, которые вы собираетесь использовать. (Это максимальная величина, и не будет проблем, если не все категории на самом деле будут применяться).
CategoryMessageFile (Optional)	String 	Файл, содержащий ресурсы строк категорий.
EventMessageFile 	String 	Файл, содержащий ресурсы строк событий.
TypesSupported 	Integer	Допустимые типы событий.
Пример кода для создания необходимых записей в реестре: 
var
 Reg: TRegistry;
 RegKey: string;
 AppPath: string;
 AppName: string;
 NumCategories: Integer;
begin
 Reg := TRegistry.Create;
 try
 AppPath := Application.ExeName;
 AppName := 'MyApplication';
 NumCategories := 2;
 RegKey := Format('SYSTEM\CurrentControlSet\Services\EventLog\Application\%s', [AppName]);
 Reg.RootKey := HKEY_LOCAL_MACHINE;
 Reg.OpenKey(RegKey, True);
 // Собственное имя
 Reg.WriteString('CategoryMessageFile', AppPath); 
 // Собственное имя
 Reg.WriteString('EventMessageFile', AppPath); 
 // Максимальное количество категорий
 Reg.WriteInteger('CategoryCount', NumCategories); 
 // Разрешаем все типы
 Reg.WriteInteger('TypesSupported', EVENTLOG_SUCCESS or EVENTLOG_ERROR_TYPE or EVENTLOG_WARNING_TYPE or EVENTLOG_INFORMATION_TYPE); 
 Reg.CloseKey;
 EventLog := RegisterEventSource(nil, PChar(AppName));
 finally
 Reg.Free;
 end;
end;
==== Сообщение и ресурсы категорий. ====
Информация, помещаемая в реестр вышеприведенным кодом, информирует журнал событий о том, где искать строки событий и категорий, основываясь на имени источника, которое использует приложение для записи в журнал. И, как мы уже говорили, чтобы журнал событий искал эти строки в нашем исполняемом файле, нам нужно включить эти строковые ресурсы в наш файл. Этот процесс состоит из следующих шагов: 
* Написание исходного файла таблицы сообщений (файл .mc). 
* Компиляция .mc файла при помощи Microsoft message compiler. 
* Подключение получившейся информации к нашему Delphi приложению.
Есть много примеров по написанию .mc файлов в Windows SDK и на различных сайтах, включая MSDN, однако документация не достаточно проста, поэтому приводим минимально достаточное описание для создания файла таблицы сообщений: 
 ;//Example Message source file exmess.mc
 MessageId=0
 Language=English
 %1
 .
 
 MessageId=1
 Language=English
 Category1
 .
 
 MessageId=2
 Language=English
 Category2
 .
 
Строки, начинающиеся с ;// являются комментариями и не компилируются. Этот пример содержит три строковых ресурса - один определитель формата сообщения и две категории, хотя файл может содержать только первый ресурс. Каждый ресурс отделен одной отдельной точкой на строке, так же, как и в конце файла. Если в конце файла отсутствует перевод строки после точки, то файл не будет скомпилирован. 
Первая строка каждого ресурса является MessageID (index), при помощи которого приложение будет обращаться к строке. 
Следующая строка указывает язык ресурса. В нашем случае "English" - означает international English, язык по умолчанию для всех Windows платформ. Информацию по многоязыковым ресурсам см. в справке к компилятору ресурсов. 
Последняя строка определяет собственно текст сообщения. В случае ресурса 0, строка будет "%1", что означает, что передается сама строка. Если, например, нужен префикс сообщения "An Event Message" (Сообщение события), то строка будет иметь вид: "An Event Message %1". 
Более полное описание форматов см. в API справке по FormatMessage и компилятору ресурсов. Ресурсы категорий не требуют форматированных аргументов. Как видно в примере, мы определили две категории "Category1" и "Category2".
Следующий этап - компиляция .mc файла при помощи Microsoft message compiler (mc.exe), который можно взять у Microsoft (входит в состав Platform SDK). Наш пример, имеющий имя "exmess.mc" может быть скомпилирован из командной строки таким образом: 
 Mc exmess.mc
В результате получаем три файла: exmess.rc, bin00001.msg и exmess.h.
* emess.h может быть использован как заголовочный файл для обращения к ресурсам по их символическим именам, если таковые указаны (в нашем примере нет).
* .bin файл это откомпилированный бинарный ресурс с сообщениями,
* .rc это файл ресурсов Windows. Он может быть откомпилирован в Delphi .res файл при помощи brcc32.exe - компилятора ресурсов Delphi или просто добавлен в проект при помощи project manager, и тогда Delphi автоматически его откомпилирует при компиляции проекта (build). 
==== Запись событий с категориями. ====
Теперь наше приложение имеет ресурсы и необходимые записи в реестре или код, который их внесет. Значит, приложение может записывать события в журнал без сообщения об отсутствии ресурсов и с добавочным индексом категории события: 
var
 EventLog: THandle;
 MyMsg: Array[0..2] of PChar;
EventLog := RegisterEventSource(nil, PChar('MyApplication'));
MyMsg[0] := 'A test event message';
ReportEvent(EventLog, EVENTLOG_INFORMATION_TYPE, 1, 0, nil, 1, 0, @MyMsg, nil);
Вышеприведенный код запишет событие в журнал с текстом "A test event message" и, потому что 1 следует за параметром EventLogType, это будет событие категории "Category1". Это достигнуто указанием 0 в качестве идентификатора события, который соответствует определителю формата в ресурсе 0 ("%1"). В результате текст сообщения события будет передан без изменения. Точно так же, категория указана 1, что соответствует "Category1" в нашем ресурсе 1.
Журнал событий поддерживает "живую связь" с файлами сообщений и категорий, указанных в реестре, что означает, что когда пользователь захочет просмотреть журнал, просмотрщик событий получит доступ к файлам ресурсов для детального отображения событий. Это также означает, что если вы создадите множество событий, при помощи указанного файла ресурсов, и, затем, измените значения в файле ресурсов и произведете обновление (refresh) в просмотрщике событий, тексты событий и номера категорий так же изменятся в соответствии с ресурсами. Точно так же, если файл ресурсов вдруг будет удален или записи в реестре будут уничтожены или повреждены, то журнал не сможет получить доступ к ресурсам, и отобразит сообщение с ошибкой в виде префикса события, как было описано в начале статьи. В этом случае вместо номера категории события будет отображен индекс категории. 
Перевод: © Digimaster 2005
Author: "FMI Solutions" 2002
Source: http://www.fmisolutions.com
ID: 02035