我正在使用WebAPI构建API,并且已经使用NLog在整个堆栈中进行日志记录。我的API解决方案有两个主要项目,包括:
我想要实现的是自动生成一个唯一的ID,我可以将其附加到日志语句,以便在处理单个请求时写入的任何日志,无论它们来自哪个层,都可以链接回原始请求。我也希望这可以在不传递唯一ID的情况下工作,或者让日志语句本身关注将它包含在他们的调用中。
考虑到这个目标,我开始考虑编写一个自定义委托处理程序来拦截每个请求(在this post之后获取指导)并在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中的请求,我是否应该考虑另一种机制?
看看LogicalCallContext。从.NET 4.5开始,它就是supports async场景。
.NET Framework有一个鲜为人知的工具,允许您将数据与“逻辑”执行线程相关联。此工具称为逻辑调用上下文,它允许数据流到其他线程,AppDomains,甚至流到其他进程中的线程。
NLog.Extension.Logging ver。 1.0能够捕获使用ILogger.BeginScope创建的上下文属性。这些可以使用NLog ${mdlc}
提取。
默认情况下,Microsoft引擎将注入RequestId
,RequestPath
等属性。