Apache log4net™ 手册 - 上下文
大多数现实世界的系统必须同时处理多个客户端。在这样的系统的典型多线程实现中,不同的线程将处理不同的客户端。日志记录特别适合跟踪和调试复杂的分布式应用程序。一种区分来自一个客户端的日志输出与另一个客户端的日志输出的方法是为每个客户端实例化一个新的独立日志记录器。但是,这会导致日志记录器的激增,并增加日志记录管理的开销。
一种更轻量级的技术是为来自同一客户端交互的每个日志请求添加唯一的标记。
Log4net 支持不同类型的上下文日志记录和具有不同范围的上下文。
范围
上下文数据可以在不同的范围内设置。这些上下文具有逐渐缩小的可见性。在日志记录事件本身中,来自所有上下文的的值将组合在一起,以便在范围较小的上下文中指定的值会隐藏来自范围较大的上下文的的值。
范围 | 类型 | 描述 |
---|---|---|
全局 | log4net.GlobalContext | 全局上下文由当前 AppDomain 中的所有线程共享。此上下文对于多个线程同时使用是线程安全的。 |
线程 | log4net.ThreadContext | 线程上下文仅对当前托管线程可见。 |
逻辑线程 | log4net.LogicalThreadContext | 逻辑线程上下文对逻辑线程可见。逻辑线程可以从一个托管线程跳转到另一个托管线程。有关更多详细信息,请参阅 .NET API System.Runtime.Remoting.Messaging.CallContext。 对于 .NET Standard,这使用 AsyncLocal 而不是 CallContext。 |
事件 | log4net.Core.LoggingEvent | 每个事件都会捕获事件生成时的当前上下文状态。可以在事件本身设置上下文数据。此上下文仅对生成事件的代码本身可见。 |
上下文属性
log4net 上下文存储属性,即名称值对。名称是字符串,值是任何对象。属性可以按如下方式设置
log4net.GlobalContext.Properties["name"] = value;
如果在多个上下文范围内设置了具有相同名称的属性,则范围最窄的范围(在上面的列表中更低)中的值将隐藏其他值。
属性值作为对象存储在 LoggingEvent 中。 PatternLayout 支持使用 %property{name} 语法渲染命名属性的值。该值通过传递给 log4net.ObjectRenderer.RendererMap 来转换为字符串,该映射将定位该值类型的任何自定义渲染器。自定义类型的默认行为是调用对象的 ToString() 方法。
活动属性值
活动属性值是指其值随时间变化的值。
例如,想象一个自定义类型,它实现了 ToString() 方法以返回运行时垃圾收集器分配的字节数。
public class GCAllocatedBytesHelper { public override string ToString() { return GC.GetTotalMemory(true).ToString(); } }
此类型的实例可以在应用程序启动期间添加到 log4net.GlobalContext 中
log4net.GlobalContext.Properties["GCAllocatedBytes"] = new GCAllocatedBytesHelper();
一旦此属性在上下文中设置,所有后续的日志记录事件都将具有一个名为 GCAllocatedBytes 的属性。该属性的值将是 GCAllocatedBytesHelper 类型的实例。当此值通过调用 ToString 方法渲染为字符串时,将返回垃圾收集器当前分配的字节数并将其包含在输出中。
上下文堆栈
有时,简单的键值对不是捕获上下文信息的最佳方式。信息堆栈是一种非常方便的存储数据的方式,尤其是在我们的应用程序倾向于基于堆栈的情况下。
ThreadContext 和 LogicalThreadContext 还支持在堆栈中存储上下文数据。堆栈存储在上下文属性中,因此堆栈具有名称,并且多个堆栈可以存在于同一个上下文中。在范围较窄的上下文中设置的属性值将覆盖在范围较广的上下文中设置的具有相同属性名称的堆栈。
堆栈支持 Push 和 Pop 方法。随着更多上下文数据被推入堆栈,堆栈会增长。当渲染堆栈时,所有推入堆栈的数据都会输出,最新数据位于字符串的右侧。
由于堆栈只是存储在上下文属性中的一个对象,因此它也使用相同的 PatternLayout 语法渲染: %property{name}。其中 name 是堆栈的名称。
对堆栈的 Push 和 Pop 方法的调用必须匹配,以便每个 push 都有一个相应的 pop。 Push 方法还返回一个 IDisposable 对象,该对象将在其被处置时执行所需的 pop 操作。这允许使用 C# using 语法来自动化堆栈管理。
using(log4net.ThreadContext.Stacks["NDC"].Push("context")) { log.Info("Message"); }
INFO 级别的日志在其 NDC 属性中存储了一个堆栈。堆栈中的顶部项目是字符串 context。 using 语法确保在块结束时,值 context 会从堆栈中弹出。
建议使用 using 语法,因为它可以减轻开发人员的一些工作量,并减少匹配 Push 和 Pop 调用时的错误,尤其是在可能发生异常的情况下。
为了说明这一点,让我们以一个向众多客户端提供内容的 Web 服务为例。Web 服务可以在执行其他代码之前,在请求的最开始构建 NDC。上下文信息可以是客户端的主机名和其他与请求相关的固有信息,通常是包含在 cookie 中的信息。因此,即使 Web 服务同时为多个客户端提供服务,由同一代码发起的日志(即属于同一个日志记录器)仍然可以区分,因为每个客户端请求将具有不同的 NDC 堆栈。将此与将新实例化的日志记录器传递给客户端请求期间执行的所有代码的复杂性进行对比。
然而,一些复杂的应用程序,如虚拟主机 Web 服务器,必须根据虚拟主机上下文以及发出请求的软件组件进行不同的日志记录。Log4net 支持多个日志记录器存储库。这将允许每个虚拟主机拥有自己的日志记录器层次结构副本。配置多个日志记录器层次结构超出了本文档的范围。