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






4.54/5 (12投票s)
2002年11月12日
8分钟阅读

155424

1767
一组 C# 类,
引言
.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 子项称为日志文件,用于定位事件日志记录服务在应用程序读写事件日志时所需的资源。默认的日志文件是Application、Security和System,您还可以创建自定义日志文件。
每个子项都有特定于该日志文件的配置值,可以使用相关的 EventLogEx
属性来读取或设置这些值。
EventSource 类
每个日志文件条目包含一个或多个称为事件源的子项,这些是向该日志文件记录事件的软件组件的名称(通常是应用程序的名称,或者如果应用程序很大,则是应用程序子组件的名称)。
事件源存储在 HKLM\System\CurrentControlSet\Services\EventLog\LogName\AppName
键下,并包含特定于将记录事件的软件的信息,这些信息可以使用相关的 EventSource
属性来读取或写入。
**注意:**您不能直接实例化 EventSource
类——而是使用现有 EventLogEx
类实例的 Source
属性。
MessageFile 属性
EventSource
对象公开 *MessageFile
属性的两个版本——Local
和 Remote
(例如 LocalEventMessageFile
和 RemoteEventMessageFile
)。
这是必要的,因为要查看远程计算机上日志中存储的事件文本,本地注册表必须有一个指定包含 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_SZ
或 REG_MULTI_SZ
类型)。
这最显著地影响了实际日志文件的路径,因为它应该位于系统目录之下,而在远程计算机上,这可能与本地计算机不同(因此您不能只使用 Environment.SystemDirectory
值)。
在路径中使用“%SystemRoot%
”而不将类型设置为 REG_SZ_EXPAND
将无法正常工作,因为它在读取时不会被解析。
我使用的解决方案是从(可能是远程的)注册表中读取“SystemRoot”值,然后使用该值来构建路径。
MCConvert 实用程序
VisualStudio.NET 不包含 MESSAGETABLE
资源的编辑器(虽然我正在开发一个),所以您目前需要像 .NET 之前一样创建消息文件(即使用 Microsoft 消息编译器 mc.exe)。
您可以创建一个消息文件,其中包含事件标识符、类别和参数的描述,或者创建三个独立的消息文件,并且多个应用程序可以共享同一个消息文件。
您通常应该将消息文件创建为仅资源 DLL,因为它们比普通 DLL 更小、加载速度更快。
- 创建 .MC 文件来定义消息资源表(有关更多信息,请参阅 MSDN)。
- 使用消息编译器从 .MC 文件创建 .RC 和 .BIN 文件:
mc filename.mc
- 使用资源编译器创建 .RES 文件:
rc -r -fo filename.res filename.rc
- 使用链接器创建 .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 日 - 首次发布