65.9K
CodeProject 正在变化。 阅读更多。
Home

增强 .NET 应用程序的 EventLog 写入

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.54/5 (12投票s)

2002年11月12日

8分钟阅读

viewsIcon

155424

downloadIcon

1767

一组 C# 类,用于解决标准 EventLog 对象的问题

引言

.NET 框架提供的 EventLog 类可以轻松访问 NT 事件日志子系统,但它有一些实现上的“特性”,限制了它的可用性(事实上,它最好被描述为一个有漏洞的抽象)。

关于这些问题的详细描述(总结如下)在 Richard Grimes 的《"Developing Applications with VisualStudio .NET"》一书中第 2.5.1 节(ISBN 0-201-70852-3)中(这里)。

本文提出的 EventLogEx 类解决了这些问题,并允许应用程序以类似于当前直接 API 调用(对于读取,现有的框架类工作正常)的方式写入事件日志。

虽然代码相当通用,但它是为了支持我正在进行的一个项目而创建的,因此,它可能存在一些在“现实世界”中显得奇怪或完全错误的设计特性——请随时剖析代码,并告诉我任何问题/评论/解决方案!

还包括一个简单的 C# 命令行实用程序,可用于将 Microsoft 消息编译器 (MC) 创建的 .h 文件转换为 C# 类(参见MCConvert 实用程序),该类可以包含在将此代码的解决方案中。

.NET 之前的事件日志记录

写入日志

当您使用标准的 NT API 调用访问事件日志时,系统会存储一个包含(除其他外)消息 ID 和消息的任何替换字符串(“插入”)的结构——但它不存储消息文本本身。

从日志读取

当您从事件日志读取条目时,系统会读取存储的消息 ID 和替换字符串,从注册表中 EventMessageFile 键中指定的文件所包含的 MESSAGETABLE 资源中获取消息文本(针对当前区域设置),插入替换字符串,然后将格式化后的字符串返回给您。

除了保持日志文件较小(这在远程计算机上访问事件日志时可以提高性能)之外,仅存储消息 ID 和替换字符串也意味着,只要客户端

  • 安装了包含该区域设置 MESSAGETABLE 的文件
  • 本地注册表已配置为告知 NT 在何处查找它

包含消息的文件只需安装在进行读取的计算机上,而无需存在于写入的计算机或保存日志的计算机上(它们都可以是不同的计算机)。

使用 .NET 进行事件日志记录

在 .NET 中,消息源会注册,其中 EventMessageFile 值始终设置为安装在 GAC 中的EventLogMessages.dll。该文件包含 65,535 个条目,每个条目包含一个字符串:%1

换句话说,对于每个可能的事件 ID,整个格式字符串都是一个占位符,它接受一个替换字符串——这个字符串始终是您传递给 EventLog.WriteEntry() 的消息。

这种方法的缺点是

  • 您有责任在写入日志之前选择用于格式化消息的区域设置,因此所有客户端都必须以相同的语言查看消息。
  • 日志文件比必需的大,因为它必须保存完整的格式化字符串,而不是仅仅是消息 ID 和替换字符串。
  • 如果您想查看远程日志上的条目,则该计算机必须安装 .NET 运行时,并且 EventLogMessages.dll 文件必须在远程计算机的 GAC 中注册。

使用 EventLogEx 进行事件日志记录

EventLogEx 类克服了这些问题,并允许您从托管代码中记录消息,根据客户端区域设置进行读时格式化消息(即,它将本机 API 调用公开为托管类)。

类概述

有关完整的 API 信息,请参阅 .ZIP 文件中包含的 NDoc 生成的文档(EventLogEx.chm)。

EventLogEx 类

事件日志记录服务使用存储在 **EventLog** 注册表项(HKLM\System\CurrentControlSet\Services\EventLog)中的信息。

EventLog 子项称为日志文件,用于定位事件日志记录服务在应用程序读写事件日志时所需的资源。默认的日志文件是ApplicationSecuritySystem,您还可以创建自定义日志文件。

每个子项都有特定于该日志文件的配置值,可以使用相关的 EventLogEx 属性来读取或设置这些值。

EventSource 类

每个日志文件条目包含一个或多个称为事件源的子项,这些是向该日志文件记录事件的软件组件的名称(通常是应用程序的名称,或者如果应用程序很大,则是应用程序子组件的名称)。

事件源存储在 HKLM\System\CurrentControlSet\Services\EventLog\LogName\AppName 键下,并包含特定于将记录事件的软件的信息,这些信息可以使用相关的 EventSource 属性来读取或写入。

**注意:**您不能直接实例化 EventSource 类——而是使用现有 EventLogEx 类实例的 Source 属性。

MessageFile 属性

EventSource 对象公开 *MessageFile 属性的两个版本——LocalRemote(例如 LocalEventMessageFileRemoteEventMessageFile)。

这是必要的,因为要查看远程计算机上日志中存储的事件文本,本地注册表必须有一个指定包含 MESSAGETABLE 资源的文件的位置的事件源条目。

如果事件正在写入自定义日志文件,则远程计算机也必须有一个类似的条目,否则 NT 事件服务将默认为将消息写入 **Application** 日志(如果写入 **Application** 日志,则该条目是可选的但推荐的)。

远程条目还将允许在其事件查看器中查看事件,前提是它也安装了 MESSAGETABLE 资源文件——但这些文件可能与本地计算机上的目录不同,因此 Remote* 属性允许您为本地和远程路径提供不同的路径(如果源是本地的,调用 Remote* 版本将不起作用)。

使用代码

EventLogEx 类应仅在写入事件时用作标准 EventLog 的替代品——在读取事件时,应使用现有的 EventLog 类。

该类的主要区别在于,它不使用 WriteEntry() 方法并向其传递字符串消息,而是使用 ReportEvent() 并向其传递消息 ID。

//
// Open a source on the local machine and then associate 
// it with a custom log called "MyCustomLog"
//
EventLogEx log = new EventLogEx( 
    "MyCustomLog", ".", "EventLogExTest" );

// Write a test message with some extra data
byte[] errorInfo = Encoding.ASCII.GetBytes( 
    new string("Some useful additional data") );

log.ReportEvent( EventLogEntryType.Warning, 0,
    (uint )EventLogExTestMessages.MSG_WITH_THREE_PARAM,
    new string[] { "The data", "below is", "the error" }, 
    errorInfo );

关注点

.NET 注册表问题

根据文档,任何注册的路径(例如,到日志文件和消息文件)都应该是 REG_SZ_EXPAND 类型,但您无法使用标准的 .NET Registry 类创建此类型条目(您只能创建 REG_SZREG_MULTI_SZ 类型)。

这最显著地影响了实际日志文件的路径,因为它应该位于系统目录之下,而在远程计算机上,这可能与本地计算机不同(因此您不能只使用 Environment.SystemDirectory 值)。

在路径中使用“%SystemRoot%”而不将类型设置为 REG_SZ_EXPAND 将无法正常工作,因为它在读取时不会被解析。

我使用的解决方案是从(可能是远程的)注册表中读取“SystemRoot”值,然后使用该值来构建路径。

MCConvert 实用程序

VisualStudio.NET 不包含 MESSAGETABLE 资源的编辑器(虽然我正在开发一个),所以您目前需要像 .NET 之前一样创建消息文件(即使用 Microsoft 消息编译器 mc.exe)。

您可以创建一个消息文件,其中包含事件标识符、类别和参数的描述,或者创建三个独立的消息文件,并且多个应用程序可以共享同一个消息文件。

您通常应该将消息文件创建为仅资源 DLL,因为它们比普通 DLL 更小、加载速度更快。

  1. 创建 .MC 文件来定义消息资源表(有关更多信息,请参阅 MSDN)。
  2. 使用消息编译器从 .MC 文件创建 .RC 和 .BIN 文件:mc filename.mc
  3. 使用资源编译器创建 .RES 文件:rc -r -fo filename.res filename.rc
  4. 使用链接器创建 .DLL 文件:link -dll -noentry -out:filename.dll filename.res

为了简化 C# 项目中使用 MC 生成的 .h 文件,.zip 文件中包含了一个消息转换器工具。该工具可用于将 .h 文件转换为 C# 文件,该文件包含一个名为 MessageIds 的公共类,该类以一组 public const uint 值形式包含消息 ID。

该实用程序使用简单:mcconvert filename.h

这将生成一个名为 filenameMessages.cs 的文件,该文件由一个名为 filenameMessages 的命名空间组成,其中包含一个 MessageIds 类,该类又公开了 public const uint 消息 ID,这些 ID 的命名与 .h 文件中的 #define 条目相同,并且还包含消息文本。

然后,可以将此文件包含在现有解决方案中,并在对 EventLogEx 类的 ReportEvent 方法的调用中使用这些 ID。

**注意:**消息 ID 被声明为 public const uint 值,而不是(对某些人而言)更自然的枚举,以便可以直接传递给 ReportEvent 方法,而无需每次都添加 (uint) 转换(并且无法轻松修改 ReportEvent 以接受枚举,因为它将为每个 .h 文件具有不同的类型)。

EventLogExTest 程序

.zip 文件包含一个用于 EventLogEx 类的简单测试程序。

要生成它,您应该首先运行 EventLogExTest 子目录中的“BuildMcDll.cmd”批处理文件,该文件会生成程序使用的 .h/.cs 文件,并编译 Test.Messages.dll 文件并将其复制到 bin\debug 目录,以便测试应用程序可以找到它。

历史

  • 2002 年 11 月 12 日 - 首次发布
© . All rights reserved.