使用Delphi写入Windows事件日志

24

我的Delphi应用程序如何轻松地写入Windows事件日志?

TEventLogger和ReportEvent有什么区别? 我该如何使用ReportEvent函数?


2
在 Stack Overflow 上搜索这个看似简单的问题会返回答案分散在许多问题之间。我创建了一个新的简单问题,并花时间将答案组合起来并添加其他答案中没有的额外信息。我这样做是因为这不是我第一次来这里寻找答案,而且我认为详细的答案和示例项目可能也会帮助其他人。 - Kobus Smit
2
你本可以在另一个问题中完成这个操作,但在这里也可以。现在这两个问题已经链接在一起了,一切都很好。http://blog.stackoverflow.com/2010/11/dr-strangedupe-or-how-i-learned-to-stop-worrying-and-love-duplication/ - David Heffernan
啊,好的,谢谢David,我现在更明白它是如何工作的了。 - Kobus Smit
1个回答

38
如果您正在编写Windows服务并需要写入本地计算机的Windows事件日志,则可以调用TService.LogMessage,如此处所述。
//TMyTestService = class(TService)

procedure TMyTestService.ServiceStart(Sender: TService; var Started: Boolean);
begin
  LogMessage('This is an error.');
  LogMessage('This is another error.', EVENTLOG_ERROR_TYPE);
  LogMessage('This is information.', EVENTLOG_INFORMATION_TYPE);
  LogMessage('This is a warning.', EVENTLOG_WARNING_TYPE);
end;

对于其他类型的应用程序,您可以使用SvcMgr.TEventLogger未记录帮助类来为TService编写本地计算机的Windows事件日志,如此处此处此处所述。

uses
  SvcMgr;

procedure TForm1.EventLoggerExampleButtonClick(Sender: TObject);
begin
  with TEventLogger.Create('My Test App Name') do
  begin
    try
      LogMessage('This is an error.');
      LogMessage('This is another error.', EVENTLOG_ERROR_TYPE);
      LogMessage('This is information.', EVENTLOG_INFORMATION_TYPE);
      LogMessage('This is a warning.', EVENTLOG_WARNING_TYPE);
    finally
      Free;
    end;
  end;
end;

你也可以使用 ReportEvent 函数,它是 Windows API 的一部分,具体可参考 这里这里

我创建了一个简单的类来使它更加容易使用,它可以在 GitHub 上 获取

//----------------- EXAMPLE USAGE: ---------------------------------

uses
  EventLog;

procedure TForm1.EventLogExampleButtonClick(Sender: TObject);
begin
  TEventLog.Source := 'My Test App Name';

  TEventLog.WriteError('This is an error.');
  TEventLog.WriteInfo('This is information.');
  TEventLog.WriteWarning('This is a warning.');
end;

//------------------------------------------------------------------

unit EventLog;

interface

type
  TEventLog = class
  private
    class procedure CheckEventLogHandle;
    class procedure Write(AEntryType: Word; AEventId: Cardinal; AMessage: string); static;
  public
    class var Source: string;
    class destructor Destroy;

    class procedure WriteInfo(AMessage: string); static;
    class procedure WriteWarning(AMessage: string); static;
    class procedure WriteError(AMessage: string); static;

    class procedure AddEventSourceToRegistry; static;
  end;

threadvar EventLogHandle: THandle;

implementation

uses Windows, Registry, SysUtils;

class destructor TEventLog.Destroy;
begin
  if EventLogHandle > 0 then
  begin
    DeregisterEventSource(EventLogHandle);
  end;
end;

class procedure TEventLog.WriteInfo(AMessage: string);
begin
  Write(EVENTLOG_INFORMATION_TYPE, 2, AMessage);
end;

class procedure TEventLog.WriteWarning(AMessage: string);
begin
  Write(EVENTLOG_WARNING_TYPE, 3, AMessage);
end;

class procedure TEventLog.WriteError(AMessage: string);
begin
  Write(EVENTLOG_ERROR_TYPE, 4, AMessage);
end;

class procedure TEventLog.CheckEventLogHandle;
begin
  if EventLogHandle = 0 then
  begin
   EventLogHandle := RegisterEventSource(nil, PChar(Source));
  end;
  if EventLogHandle <= 0 then
  begin
    raise Exception.Create('Could not obtain Event Log handle.');
  end;
end;

class procedure TEventLog.Write(AEntryType: Word; AEventId: Cardinal; AMessage: string);
begin
  CheckEventLogHandle;
  ReportEvent(EventLogHandle, AEntryType, 0, AEventId, nil, 1, 0, @AMessage, nil);
end;

// This requires admin rights. Typically called once-off during the application's installation
class procedure TEventLog.AddEventSourceToRegistry;
var
  reg: TRegistry;
begin
  reg := TRegistry.Create;
  try
    reg.RootKey := HKEY_LOCAL_MACHINE;
    if reg.OpenKey('\SYSTEM\CurrentControlSet\Services\Eventlog\Application\' + Source, True) then
    begin
      reg.WriteString('EventMessageFile', ParamStr(0)); // The application exe's path
      reg.WriteInteger('TypesSupported', 7);
      reg.CloseKey;
    end
    else
    begin
      raise Exception.Create('Error updating the registry. This action requires administrative rights.');
    end;
  finally
    reg.Free;
  end;
end;

initialization

TEventLog.Source := 'My Application Name';

end.

ReportEvent支持向本地或远程计算机的事件日志写入日志条目。有关远程示例,请参见John Kaster的EDN文章
请注意,否则您的所有日志消息都将以以下内容开头:您还需要创建消息文件注册事件源

找不到源自 xxxx 的事件 ID xxx 的说明。引发此事件的组件未安装在本地计算机上,或者安装已损坏。您可以在本地计算机上安装或修复组件。

如果事件源来自另一台计算机,则必须保存显示信息才能显示该事件。

事件附带以下信息:

1,关于如何创建消息文件的更多信息,请参见Finn Tolderlund的教程Michael Hex的文章,或者您可以使用现有的MC和GitHub项目中包含的RES文件

2,通过在DPR文件中包含MessageFile.res将RES文件嵌入到您的应用程序中。或者,您可以为消息创建一个dll。

program MyTestApp;

uses
  Forms,
  FormMain in 'FormMain.pas' {MainForm},
  EventLog in 'EventLog.pas';

{$R *.res}
{$R MessageFile\MessageFile.res}

begin
  Application.Initialize;

3、一次性注册需要管理员权限写入注册表,因此通常作为应用程序安装过程的一部分完成。

//For example
AddEventSourceToRegistry('My Application Name', ParamStr(0));
//or
AddEventSourceToRegistry('My Application Name', 'C:\Program Files\MyApp\Messages.dll');

//--------------------------------------------------

procedure AddEventSourceToRegistry(ASource, AFilename: string);
var
  reg: TRegistry;
begin
  reg := TRegistry.Create;
  try
    reg.RootKey := HKEY_LOCAL_MACHINE;
    if reg.OpenKey('\SYSTEM\CurrentControlSet\Services\Eventlog\Application\' + ASource, True) then
    begin
      reg.WriteString('EventMessageFile', AFilename);
      reg.WriteInteger('TypesSupported', 7);
      reg.CloseKey;
    end
    else
    begin
      raise Exception.Create('Error updating the registry. This action requires administrative rights.');
    end;
  finally
    reg.Free;
  end;
end;

如果您需要Windows事件记录和其他日志记录需求,您还可以使用诸如log4dTraceTool等日志框架。


如果您想在Delphi IDE中写入事件日志窗口,请参见此处


4
做得好!只需针对您的日志类进行调整。我更喜欢他们的方法是实例方法,而不是类方法,以避免重复注册和注销事件源。当创建类的实例时,我会注册事件源,并在销毁时注销。或者使用全局线程变量并初始化一次。 - TLama
如果在实例被销毁时注销,那么是否会导致现有的事件日志条目无法再被读取?换句话说,操作员必须让实例保持活动状态才能查看旧的事件日志条目吗?我认为注册事件源应该是安装的一部分,而注销则应该是可执行文件的卸载过程中的一部分。 - Ondrej Kelle
嗨TOndrey,我怀疑TLama指的是“RegisterEventSource”API调用,而不是“通过将其添加到注册表中注册事件源”:-) - Kobus Smit
嗨,TLama,感谢您的建议。我最初认为那可能是微小的优化,而且我喜欢类方法的一行易用性 :-) - Kobus Smit
这个实现在除了Windows 10之外的其他系统上都能正常工作。当运行答案中的代码时,它可以正确地记录消息,但会导致应用程序mmc.exe和consent.exe出错。一个可能的解决方法可以在这里找到 - https://social.technet.microsoft.com/Forums/en-US/fb7eea4a-c2ac-4c84-a9e5-a6063b04f4a2/uac-prompts-not-working-in-windows-10-pro-1511-or-1703?forum=win10itprogeneral。赞一个将所有资源汇集到一个单独的线程中。 - RBA
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接