如何优雅地记录上下文信息以及每条消息

Naw*_*waz 5 c# logging enterprise-library

我正在寻找一些关于伐木的建议.我编写了一个Logger 内部使用Microsoft Enterprise Library 5.0的包装器.目前,它使我们能够以这种方式登录:

Logger.Error(LogCategory.Server, "Some message with some state {0}", state);
Run Code Online (Sandbox Code Playgroud)

我面临的问题是EventViewer中的每个日志似乎都是无关的,即使它们中的一些以某种方式相关,例如它们都来自处理来自特定客户端的请求.

让我详细说明这个问题.假设我正在处理一个需要同时处理来自多个客户端的请求的服务,每个服务都将不同的参数集传递给服务方法.很少有参数可用于识别请求,例如哪个客户端使用哪些唯一可识别的参数进行何种请求等.说这些参数是(调用上下文信息):

  • ServerProfileId
  • WebProfileId
  • 的requestId
  • SessionInfo

现在服务开始处理请求,一个接一个地做(比如工作流).在这个过程中,我正在记录本地1条消息,例如"找不到文件""在DB中找不到条目",但我不想手动记录上述信息(上下文信息)与每个日志,而是我想要记录器每次记录本地消息时自动记录它们:

Logger.Error(LogCategory.Server, "requested file not found");
Run Code Online (Sandbox Code Playgroud)

我希望上面的调用记录上下文信息以及消息"未找到请求的文件",以便我可以将消息与其上下文相关联.

问题是,我应该如何设计一个自动记录上下文的记录器包装器?我希望它足够灵活,以便我可以在服务处理请求的过程中添加更具体的上下文信息,因为在请求开始时可能无法获得所有重要信息!

我也想让它可配置,所以我可以记录本地消息而不记录上下文信息,因为当一切正常时它们不需要它们.:-)


1.通过本地消息,我指的是更具体,本地性质的消息.相比之下,我会说,上下文信息是全局消息,因为它们对处理请求的整个流程有意义.

Ran*_*ica 4

这是一种使用企业库的方法,该方法相当容易设置。您可以使用活动跟踪来存储全局上下文,使用扩展属性来存储本地上下文。

为了举例,我将使用不带任何包装类的服务定位器来演示该方法。

var traceManager = EnterpriseLibraryContainer.Current.GetInstance<TraceManager>();

using (var tracer1 = traceManager.StartTrace("MyRequestId=" + GetRequestId().ToString()))
using (var tracer2 = traceManager.StartTrace("ClientID=" + clientId))
{
    DoSomething();
}

static void DoSomething()
{
    var logWriter = EnterpriseLibraryContainer.Current.GetInstance<LogWriter>();
    logWriter.Write("doing something", "General");

    DoSomethingElse("ABC.txt");
}

static void DoSomethingElse(string fileName)
{
    var logWriter = EnterpriseLibraryContainer.Current.GetInstance<LogWriter>();

    // Oops need to log
    LogEntry logEntry = new LogEntry()
    {
        Categories = new string[] { "General" },
        Message = "requested file not found",
        ExtendedProperties = new Dictionary<string, object>() { { "filename", fileName } }
    };

    logWriter.Write(logEntry);
}
Run Code Online (Sandbox Code Playgroud)

配置如下所示:

var traceManager = EnterpriseLibraryContainer.Current.GetInstance<TraceManager>();

using (var tracer1 = traceManager.StartTrace("MyRequestId=" + GetRequestId().ToString()))
using (var tracer2 = traceManager.StartTrace("ClientID=" + clientId))
{
    DoSomething();
}

static void DoSomething()
{
    var logWriter = EnterpriseLibraryContainer.Current.GetInstance<LogWriter>();
    logWriter.Write("doing something", "General");

    DoSomethingElse("ABC.txt");
}

static void DoSomethingElse(string fileName)
{
    var logWriter = EnterpriseLibraryContainer.Current.GetInstance<LogWriter>();

    // Oops need to log
    LogEntry logEntry = new LogEntry()
    {
        Categories = new string[] { "General" },
        Message = "requested file not found",
        ExtendedProperties = new Dictionary<string, object>() { { "filename", fileName } }
    };

    logWriter.Write(logEntry);
}
Run Code Online (Sandbox Code Playgroud)

这将产生如下输出:

----------------------------------------
Timestamp: 1/16/2013 3:50:11 PM
Message: doing something
ActivityID: 5b765d8c-935a-445c-b9fb-bde4db73124f
Context: General, ClientID=123456, MyRequestId=8f2828be-44bf-436c-9e24-9641963db09a
Priority: -1
EventId: 1
Severity: Information
Title:
Machine: MACHINE
App Domain: LoggingTracerNDC.exe
ProcessId: 5320
Process Name: LoggingTracerNDC.exe
Thread Name: 
Win32 ThreadId:8472
Local Context: 
----------------------------------------
----------------------------------------
Timestamp: 1/16/2013 3:50:11 PM
Message: requested file not found
ActivityID: 5b765d8c-935a-445c-b9fb-bde4db73124f
Context: General, ClientID=123456, MyRequestId=8f2828be-44bf-436c-9e24-9641963db09a
Priority: -1
EventId: 0
Severity: Information
Title:
Machine: MACHINE
App Domain: LoggingTracerNDC.exe
ProcessId: 5320
Process Name: LoggingTracerNDC.exe
Thread Name: 
Win32 ThreadId:8472
Local Context: filename - ABC.txt

----------------------------------------
Run Code Online (Sandbox Code Playgroud)

注意事项:

  • 由于我们使用跟踪,因此我们可以免费获得 .NET 活动 ID,该 ID 可用于关联活动。当然,我们也可以使用自己的上下文信息(自定义请求 ID、客户端 ID 等)。
  • 企业库使用跟踪“操作名称”作为类别,因此我们需要设置 logWarningsWhenNoCategoriesMatch="false" 否则我们会收到一系列警告消息。
  • 这种方法的一个缺点可能是性能(但我还没有测量它)。

如果您想禁用全局上下文(在此实现中,即跟踪),那么您需要做的就是编辑配置文件并设置tracingEnabled =“false”。

这似乎是使用内置企业库功能实现目标的相当直接的方法。

其他需要考虑的方法可能是使用某种拦截(自定义 LogCallHandler),这可能非常优雅(但这可能取决于现有的设计)。

如果您打算使用自定义实现来收集和管理上下文,那么您可以考虑使用Trace.CorrelationManager来处理每个线程上下文。您还可以考虑创建一个IExtraInformationProvider来填充扩展属性字典( 有关示例,请参阅Enterprise Library 3.1 日志格式化程序模板 - 包括 URL 请求)。

  • 实际的 LogWriter 实现类作为 Singleton 向容器注册,因此您将始终获得相同的 LogWriter 实例;并发是在内部处理的。Tracer 实际上在幕后使用 CorrelationManager,因此所有上下文信息都存储在线程绑定上下文中。每个线程都有它自己的上下文。 (2认同)
  • 上下文信息将(应该)填充在逻辑操作内创建的线程中。例如,如果我将 `DoSomethingElse("ABC.txt");` 更改为 `ThreadPool.QueueUserWorkItem(new WaitCallback(DoSomethingElse), "ABC.txt");` 输出将是相同的。有关线程示例,请参阅 CorrelationManager 类](http://msdn.microsoft.com/en-us/library/system.diagnostics.correlationmanager.aspx)。 (2认同)