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






4.76/5 (17投票s)
2006年10月28日
12分钟阅读

74324

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 提供的各种类和方法中,Category
和 Priority
属性是松散类型的。Category
是一个 string
,而 Priority
是一个 int
。为了实现一个通用的可重用框架,这些属性确实需要是松散类型的,以满足不同的应用程序需求。但是,对于单个应用程序,提供对 Category
和 Priority
值的更严格控制是有用的,因为它们在控制日志记录的位置和内容方面非常重要。我不想让一个大团队的开发人员随意使用 Category
和 Priority
值。确保 Category
和 Priority
的使用方式一致非常重要。
当我开始研究 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),来限制 Category
、Priority
和 Operation
的可用值,并结合硬编码的 Tracer
值 5 来定义 Priority
。
策略
Microsoft 提供了 Enterprise Library 的源代码,并且可以修改源代码供自己使用。但是,我不想更改 Microsoft 提供的源代码。相反,我想通过继承或提供定制的包装器来扩展 Microsoft 提供的类。
我们提供更健壮的日志记录和跟踪框架的策略如下:
- 为适合我们应用程序的
Category
和Priority
值定义枚举。Category
枚举成员的名称将与 Logging Application Block 配置中的 Category 值匹配。 - 提供我们自己的类来包装 Logging Application Block 中提供的
LogEntry
、Logger
和Tracer
类。这些类提供的接口与 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
类具有与基类类似的构造函数,只是类别和优先级值的类型是 Category
和 Priority
枚举。每个构造函数在将枚举转换为等效的 string
和 int
值后,都会调用基类构造函数。
新的 LogEntry
类还取代了 Category
和 Priority
属性。这些属性的类型是 Category
和 Priority
枚举。这些属性在枚举值和等效的 string
和 int
值之间进行转换后,映射到基类属性。
处理类别集合
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
类具有与基类类似的构造函数,只是名为 operation
的 string
参数已被名为 category
的 Category
枚举参数替换。每个构造函数在将枚举转换为等效的 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
类作为您自己的日志记录程序集的基础。确保包含 LogEntry
、Logger
和 Tracer
类。修改 Globals.cs 文件中的 Priority
和 Category
枚举以满足您的需求。
在您自己的应用程序项目中添加对您的日志记录程序集以及 Enterprise Library 的引用。
使用 Enterprise Library 配置工具修改您应用程序的配置文件中的日志记录配置设置。确保配置中的 Category 值和 Category
枚举的值是一致的。