将所有NLog日志绑定回WebAPI中的原始请求的方法

本文关键字:原始 请求 方法 WebAPI NLog 日志 绑定 | 更新日期: 2023-09-27 17:52:44

我正在使用WebAPI构建一个API,并且一直使用NLog在整个堆栈中进行日志记录。我的API解决方案有两个主要项目,包括:

  • 网站层本身,实现控制器和webapi的东西
  • 以类似cqrs的方式实现"异步"命令和处理程序的服务层

我想要实现的是自动生成一个唯一的ID,我可以附加到日志语句,以便在服务单个请求时编写的任何日志,无论它们来自哪个层,都可以链接回原始请求。我也希望这种工作不需要传递唯一的ID,或者让日志语句本身关心在调用中包含它。

带着这个目标,我开始考虑编写一个自定义委派处理程序来拦截每个请求(参考这篇文章的指导),并在NLog中添加一个唯一的ID作为属性。最后我写了以下内容:
/// <summary>
/// This class is a WebAPI message handler that helps establish the data and operations needed
/// to associate log statements through the entire stack back to the originating request.
/// 
/// Help from here: http://weblogs.asp.net/fredriknormen/log-message-request-and-response-in-asp-net-webapi
/// </summary>
public class InitializeLoggingMessageHandler : DelegatingHandler
{
    private ILogger _logger;
    // The logger is injected with Autofac
    //
    public InitializeLoggingMessageHandler(ILogger logger)
    {
        _logger = logger;
    }
    protected async override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
    {
        // Get a unique ID for this request
        //
        var uniqueId = Guid.NewGuid().ToString();
        // Now that we have a unique ID for this request, we add it to NLog's MDC as a property
        //  we can use in the log layouts. We do NOT use the global diagnostic context because
        //  WebAPI is multi-threaded, and we want the ID to be scoped to just the thread servicing
        //  this request.
        //
        NLog.MappedDiagnosticsContext.Set("UniqueId", uniqueId);
        // Capture some details about the request for logging
        //
        var requestInfo = string.Format("{0} {1}", request.Method, request.RequestUri);
        var requestMessage = await request.Content.ReadAsByteArrayAsync();
        _logger.Info("Request: {0} - {1}", requestInfo, Encoding.UTF8.GetString(requestMessage));
        var response = await base.SendAsync(request, cancellationToken);
        return response;
    }
}

有了这段代码,我可以在日志布局中使用唯一的ID,如下所示:

<target xsi:type="Debugger" name="DebugLogger" 
        layout="${longdate} ${logger} ${mdc:item=UniqueId} ${message}" />

这种方法的问题是,我使用NLog的MappedDiagnosticsContext试图将唯一ID保存为可以在布局中使用的属性(所以我的代码做日志记录不需要知道)。这是一种用于存储值的线程本地机制,因此当您使用异步代码时,它就会失效,因为启动请求的线程可能不是执行所有请求的线程。

所以发生的事情是第一个日志消息包含唯一ID,但后来的日志消息可能会丢失它,因为它们在不同的线程上,无法访问该值。我也不能在NLog中使用GlobalDiagnosticsContext,因为它是真正的全局的,所以WebAPI中的多个请求很容易覆盖唯一的ID,数据将是无用的。

那么,为了将所有日志消息关联回WebAPI中发起的请求,我是否应该考虑另一种机制?

将所有NLog日志绑定回WebAPI中的原始请求的方法

看一下LogicalCallContext。从。net 4.5开始,它支持异步场景。

。杰弗里·里:

. net框架有一个鲜为人知的功能,允许您将数据与"逻辑"执行线程相关联。这种能力被称为逻辑调用上下文,它允许数据流到其他线程,appdomain,甚至其他进程中的线程。

NLog.Extension。记录版本。1.0能够捕获用ILogger.BeginScope创建的上下文属性。这些可以使用NLog ${mdlc}提取。

微软引擎将默认注入属性如RequestId, RequestPath

参见:https://github.com/NLog/NLog.Extensions.Logging/wiki/NLog-properties-with-Microsoft-Extension-Logging

如果你正在使用应用程序洞察,他们自动设置System.Diagnostics.Activity.Current到一个对象,有所有的应用程序洞察信息,你可能想要和更多,包括RootIdId,让你与其他事件相关。

查看这个答案了解更多细节以及如何使用nlog轻松记录。