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

本文关键字:消息 上下文 信息 何优雅 记录 | 更新日期: 2023-09-27 17:53:47

我正在寻找一些关于日志记录的建议。我编写了一个名为Logger的包装器,它内部使用Microsoft Enterprise Library 5.0。目前它允许我们以这种方式登录:

Logger.Error(LogCategory.Server, "Some message with some state {0}", state);

我面临的问题是,EventViewer中的每个日志似乎都不相关,即使其中一些在某种程度上是相关的,例如,它们都来自处理来自特定客户端的请求。

让我详细说明这个问题。假设我正在处理一个服务,该服务需要同时处理来自多个客户机的请求,每个客户机将不同的参数集传递给服务方法。少数参数可以使用来识别请求,例如哪个客户端正在发出什么类型的请求使用什么唯一可识别的参数,等等。假设这些参数是(调用上下文信息):
  • ServerProfileId
  • WebProfileId
  • RequestId
  • SessionInfo

现在服务开始处理请求,做一件事接着另一件事(像工作流一样)。在此过程中,我记录了本地1消息,如"未找到文件""在DB中未找到条目",但我不想在每个日志中手动记录上述信息(上下文信息),而是希望日志记录器在每次记录本地消息时自动记录它们:

Logger.Error(LogCategory.Server, "requested file not found");

并且我希望上面的调用记录上下文信息以及消息"请求的文件未找到",因此我可以将消息与其上下文关联起来。

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

我还想使其可配置,这样我就可以记录本地消息而不记录上下文信息,因为当一切正常工作时不需要它们。: -)


<一口> 1。通过local message,我指的是更具体的本地消息。相比之下,我认为上下文信息是全局消息,因为它们对于处理请求的整个流程是有意义的。

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

这里有一种使用Enterprise Library的方法,它相当容易设置。您可以使用活动跟踪来存储全局上下文,使用扩展属性来存储本地上下文。

为了作为示例,我将使用一个没有任何包装器类的服务定位器来演示这种方法。

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);
}

配置应该是这样的:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <section name="loggingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" />
    </configSections>
    <loggingConfiguration name="" tracingEnabled="true" defaultCategory="General"
        logWarningsWhenNoCategoriesMatch="false">
        <listeners>
            <add name="Flat File Trace Listener" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.FlatFileTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.FlatFileTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                fileName="trace.log" formatter="Text Formatter" traceOutputOptions="LogicalOperationStack" />
        </listeners>
        <formatters>
            <add type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                template="Timestamp: {timestamp}&#xA;Message: {message}&#xA;ActivityID: {activity}&#xA;Context: {category}&#xA;Priority: {priority}&#xA;EventId: {eventid}&#xA;Severity: {severity}&#xA;Title:{title}&#xA;Machine: {localMachine}&#xA;App Domain: {localAppDomain}&#xA;ProcessId: {localProcessId}&#xA;Process Name: {localProcessName}&#xA;Thread Name: {threadName}&#xA;Win32 ThreadId:{win32ThreadId}&#xA;Local Context: {dictionary({key} - {value}{newline})}"
                name="Text Formatter" />
        </formatters>
        <categorySources>
            <add switchValue="All" name="General">
                <listeners>
                    <add name="Flat File Trace Listener" />
                </listeners>
            </add>
        </categorySources>
        <specialSources>
            <allEvents switchValue="All" name="All Events" />
            <notProcessed switchValue="All" name="Unprocessed Category" />
            <errors switchValue="All" name="Logging Errors &amp; Warnings" />
        </specialSources>
    </loggingConfiguration>
</configuration>

这将导致如下输出:

----------------------------------------
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
----------------------------------------

注意事项:

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

如果你想禁用全局上下文(在这个实现中是跟踪),那么你所需要做的就是编辑配置文件并设置tracingEnabled="false"。

这似乎是一个相当直接的方式来实现您的目标,使用内置的企业库功能。

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

如果您打算使用自定义实现来收集和管理上下文,那么您可以考虑使用Trace。每个线程上下文的CorrelationManager。你也可以创建一个IExtraInformationProvider来填充扩展属性字典(参见Enterprise Library 3.1 Logging Formatter Template - Include URL Request)。

免责声明:我不知道微软技术栈的细节

在这种情况下,我要做的是:

实现一个Singleton(在服务器实例级别,如果你的应用被部署到集群中,它不需要是全局Singleton),它基本上是一种Hashmap,其中"Key"是你正在处理的请求的唯一有意义的标识符。在Java中,我将使用thread-id(假设您通过从线程池中调度线程来服务请求)。如果这在您的情况下是不可能的/没有意义的,您必须使用请求ID(但在这种情况下,您必须使它渗透到日志管理器,而我的想法是使用线程本身固有可用的东西)。

Hashmap的"值"将是一组数据-在您的情况下是ServerProfileId, WebProfileId, requesttid(除非它已经是键),SessionInfo -以便日志管理器可以有条件地检索这些数据并挑选对特定日志事件有意义的部分。

因此:在请求管理开始时创建一个全局可用的请求状态引用,并确保日志管理器可以在需要时检索请求状态

当然,您必须确保hashmap被正确管理(通过在处理请求后删除状态)。