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

Enterprise Library日志应用程序块的扩展

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.76/5 (17投票s)

2006年10月28日

12分钟阅读

viewsIcon

74324

downloadIcon

1528

通过扩展 Enterprise Library Logging Application Block 来提供对日志记录的更多控制。

引言

Logging Application Block)(Microsoft Enterprise Library 的一部分)提供了一个出色的日志记录和跟踪框架。最近,我在一个团队项目中引入了 Logging Application Block。在研究如何使用 Logging Application Block 时,我发现了一些地方,我希望能够更严格地控制团队开发人员如何使用该应用程序块。在本文中,我将解释这样做的原因,并向您展示如何扩展该应用程序块。

本文以及相关的源代码和演示项目基于 Enterprise Library for .NET Framework 2.0 (2006年1月)。本文假设您已经了解了 Logging Application Block 的基础知识。要了解基础知识,请参阅 Piers Lawson 在 CodeProject 上的文章:Get Logging with the Enterprise Library。虽然那篇文章基于 Enterprise Library for .NET 1.1 版本,但对于后续版本仍然适用。

背景

Logging Application Block 的一项功能是通过配置来控制哪些消息被记录以及它们被记录到何处。日志条目可以分组到类别(Categories)中。每个类别都可以独立配置以特定方式路由和格式化日志条目。类别也可以用于过滤日志条目,允许忽略某些类别。日志条目还可以被分配优先级(Priority),优先级也可用于过滤日志条目。

在 Logging Application Block 提供的各种类和方法中,CategoryPriority 属性是松散类型的。Category 是一个 string,而 Priority 是一个 int。为了实现一个通用的可重用框架,这些属性确实需要是松散类型的,以满足不同的应用程序需求。但是,对于单个应用程序,提供对 CategoryPriority 值的更严格控制是有用的,因为它们在控制日志记录的位置和内容方面非常重要。我不想让一个大团队的开发人员随意使用 CategoryPriority 值。确保 CategoryPriority 的使用方式一致非常重要。

当我开始研究 Logging Application Block 时,我搜索了关于 Priority 值使用的指导。我发现许多人推荐使用 3=High、2=Medium、1=Low 等值。然而,正如 Piers Lawson 在文章 Get Logging with the Enterprise Library 中提到的,Tracer 编写的日志条目具有硬编码的 Priority 值 5。Tracer 类不公开 Priority 属性,并且 MSDN 上关于 Logging Application Block 的文档也没有提及它。它以 Priority 值为 5 进行日志记录的事实,只能通过查看应用程序块的代码来发现。鉴于 Priority Filter 的工作方式,为与 Trace 消息的 5 值一起工作的 Priority 值提供结构很重要。

另一个问题是,Tracer 类构造函数中提供的 operation 参数实际上映射到日志消息的 Category。由于 operation 参数只是一个 string,开发人员很容易随意创建 operation 值。

理想情况下,应提供一种机制,例如枚举(enums),来限制 CategoryPriorityOperation 的可用值,并结合硬编码的 Tracer 值 5 来定义 Priority

策略

Microsoft 提供了 Enterprise Library 的源代码,并且可以修改源代码供自己使用。但是,我不想更改 Microsoft 提供的源代码。相反,我想通过继承或提供定制的包装器来扩展 Microsoft 提供的类。

我们提供更健壮的日志记录和跟踪框架的策略如下:

  • 为适合我们应用程序的 CategoryPriority 值定义枚举。Category 枚举成员的名称将与 Logging Application Block 配置中的 Category 值匹配。
  • 提供我们自己的类来包装 Logging Application Block 中提供的 LogEntryLoggerTracer 类。这些类提供的接口与 Enterprise Library 类基本相同,但参数和属性是枚举而不是字符串和整数。我们的方法、属性和构造函数在转换类型后调用底层类。Tracer 类的操作使用 Category 枚举。

正如您将在下面看到的,我需要更改 Logging Application Block 中的一行代码。此更改已提交给 Microsoft 的 Patterns & Practices 组,并且预计将在 Enterprise Library 的下一个版本中包含此更改。

附加解决方案中的 EntLibLoggingExtended 项目提供了这些类的实现。枚举定义在 Globals.cs 文件中。任何想要使用此方法的项目都需要包含 EntLibLoggingExtended 程序集,并根据应用程序需求更新枚举值。

LogEntry 类

Logging Application Block 中的 LogEntry 类代表一个日志消息。此类包含所有日志消息所需的通用属性。

在 EntLibLoggingExtended 项目中,我创建了一个新的 LogEntry 类,该类继承自 Logging Application Block 的 LogEntry 类。新的 LogEntry 类具有与基类类似的构造函数,只是类别和优先级值的类型是 CategoryPriority 枚举。每个构造函数在将枚举转换为等效的 stringint 值后,都会调用基类构造函数。

新的 LogEntry 类还取代了 CategoryPriority 属性。这些属性的类型是 CategoryPriority 枚举。这些属性在枚举值和等效的 stringint 值之间进行转换后,映射到基类属性。

处理类别集合

Categories 属性是一个集合。为了使 Categories 属性正常工作,我需要创建自己的 CategoriesCollection 类。当一个 Category 被添加到集合中或从集合中移除时,它也需要被添加到或从基类字符串集合中添加或移除。CategoriesCollection 类在 Category 被添加或移除时会引发事件。通过在 LogEntry 类中处理这些事件,我可以添加或移除元素到基类的 string 值的 Categories 集合。

Logger 类

Logger 类是写入日志条目的外观(facade)。Logging Application Block 中的此类是 static 的,因此也是 sealed 的。它不能被继承。

在 EntLibLoggingExtended 项目中,我创建了一个新的 Logger 类,它是 Logging Application Block 的 Logger 类的包装器。对于 Logging Application Block 的 Logger 类中的每个公共方法,我在新的 Logger 类中实现了相同的方法。所有具有 string 类型 category 参数的方法,现在都具有 Category 枚举类型的 category 参数。所有具有 int 类型 priority 参数的方法,现在都具有 Priority 枚举类型的 priority 参数。每个方法最终都会调用 Logging Application Block 的 Logger 类中的相应方法。

为了进一步提高日志记录的一致性,扩展后的 Logger 类除了提供应用程序块 Logger 类方法的包装器外,还提供了许多针对常见消息类型的附加方法,例如记录断言、调试消息和跟踪消息,以及写入信息、警告和错误消息。

Tracer 类

Tracer 类表示一个性能跟踪类,用于记录方法的进入/退出和持续时间。Tracer 对象的生命周期将决定跟踪的开始和结束。构造函数会写入一条跟踪消息,当对象被释放时(该类实现了 IDisposable),会写入第二条跟踪消息。

在 EntLibLoggingExtended 项目中,我创建了一个新的 Tracer 类,该类继承自 Logging Application Block 的 Tracer 类。新的 Tracer 类具有与基类类似的构造函数,只是名为 operationstring 参数已被名为 categoryCategory 枚举参数替换。每个构造函数在将枚举转换为等效的 string 值后,都会调用基类构造函数。

一个默认构造函数

Enterprise Library 中的 Tracer 类不提供默认构造函数。每次使用该类时,开发人员都必须在构造函数中提供一个 operation(即 Category)。在几乎所有使用 Tracer 类的情况下,我们都只是想要一个类别的默认值。我已经为扩展的 Tracer 类提供了一个默认构造函数。这会创建一个 Category 为“Trace”的 Tracer。只有偶尔我们会创建一个具有其他 Category 的 Tracer

减少不跟踪时的开销

在 Enterprise Library 中 Tracer 类的实现中,该类必须执行一些工作,才能决定是否启用了跟踪以及是否正在记录跟踪消息的类别。我们的目标是将跟踪包含在几乎所有方法中,并且我担心创建每个 Tracer 实例所需工作的潜在开销。为了避免在生产环境中产生此开销,在 EntLibLoggingExtended 程序集的 Release 构建中,我们的 Tracer 的整个实现都将被编译出去。Tracer 变得非常轻量级。Tracer 类的公共接口保持不变。只有内部实现被移除。这意味着使用 Tracer 的应用程序代码在 Debug 和 Release 构建之间不需要更改。

您将在 Tracer 类的代码中看到,在 Release 构建中,该类不使用 Logging Application Block 的 Tracer 类作为基类,并且调用基类构造函数的构造函数实现也被移除了。但是,使用 Tracer 类的代码期望该类是可处置的,并且可处置性由基类提供。因此,在 Release 构建中,该类需要直接实现 IDisposable

这确实意味着需要 EntLibLoggingExtended 程序集的 Debug 构建来进行跟踪。但是,它使得 Tracer 在 Release 构建中非常轻量级,并避免了确定是否启用跟踪的任何性能影响。将每个方法的内容包装在 using (new Tracer()) {...} 块中几乎没有开销。

为了与默认构造函数协同工作,我们创建了一个代码片段,该片段插入以下代码:

using (new Tracer())
{
}

每次创建新方法时,都会立即使用该代码片段,然后将方法的实现添加到此块内。

一个问题

Tracer 类需要确定正在跟踪的方法。也就是说,创建 Tracer 类新实例的方法。Logging Application Block 中的 Tracer 类通过向后遍历当前堆栈跟踪,直到找到一个不属于 Tracer 类本身的方法来实现这一点。执行此操作的代码行是以下 if 语句:

if (method.DeclaringType != GetType())

这在正在跟踪的方法创建 Logging Application Block 中提供的 Tracer 类实例时有效。但是,当正在跟踪的方法创建 EntLibLoggingExtended 程序集中的 Tracer 类实例时,跟踪消息总是将正在跟踪的方法报告为 EntLibLoggingExtended 程序集中的 Tracer 类构造函数。这并没有什么帮助。

一个解决方案

为了克服这个问题,我需要对 Logging Application Block 中的 Tracer 类进行一项非常小的更改(一行)。上面显示的这行代码位于 Tracer 类中的 GetExecutingMethodName 方法中。这是 Enterprise Library Logging Application Block (Jan 2006) 的 Tracer.cs 文件(Microsoft.Practices.EnterpriseLibrary.Logging 程序集)的第 264 行。此行从

if (method.DeclaringType != GetType())

to

if (!typeof(Tracer).IsAssignableFrom(method.DeclaringType))

有了这个更改,GetExecutingMethodName 方法找到的执行方法将是调用我们子类的那个方法。使用原始代码,if 测试将满足堆栈跟踪中的第一个不属于基类 Tracer 的方法。使用新代码,if 测试仅满足堆栈跟踪中第一个不属于基类 Tracer 或其任何子类(包括我们的派生 Tracer 类)的方法。

演示 Visual Studio 解决方案

演示 Visual Studio 解决方案演示了对 Logging Application Block 中的 Tracer 类的更改、新的包装器类以及一个更新版本的 Logging Quick Start(随 Enterprise Library 提供),该版本使用了我们的包装器类。

解决方案中有三个项目:

  • Logging – 这是 Enterprise Library Logging Application Block,进行了一行代码更改。
  • EntLibLoggingExtended – 这是一个具有新包装器类的类库。
  • EntLibLoggingExQuickStart – 这是 Enterprise Library Logging Quick Start,已修改为使用包装器类。

Using the Code

如果您还没有这样做,请在此处下载并安装 Enterprise Library:here

修改 Enterprise Library 的 Logging 项目中 Tracer.cs 文件的第 264 行,如上所示,然后重新编译 Enterprise Library。

使用演示解决方案中的 EntLibLoggingExtended 类作为您自己的日志记录程序集的基础。确保包含 LogEntryLoggerTracer 类。修改 Globals.cs 文件中的 PriorityCategory 枚举以满足您的需求。

在您自己的应用程序项目中添加对您的日志记录程序集以及 Enterprise Library 的引用。

使用 Enterprise Library 配置工具修改您应用程序的配置文件中的日志记录配置设置。确保配置中的 Category 值和 Category 枚举的值是一致的。

© . All rights reserved.