将所有 NLog 日志绑定回 WebAPI 中的原始请求的方法?
Approach for tying all NLog logs back to the original request within WebAPI?
我正在使用 WebAPI 构建一个 API,并且一直在使用 NLog 在整个堆栈中进行日志记录。我的 API 解决方案有两个主要项目,包括:
- 实现控制器和 webapi 东西的网站层本身
- 以类似 CQRS 的方式实现 "async" 命令和处理程序的服务层
我想要实现的是自动生成一个唯一的 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,并且数据将毫无用处。
因此,为了将所有日志消息关联回 Web 中发起的请求API,我是否应该考虑另一种机制?
看看 LogicalCallContext. As of .NET 4.5, it supports async 个场景。
The .NET Framework has a little-known facility that allows you to associate data with a “logical” thread-of-execution. This facility is called logical
call context and it allows data to flow to other threads, AppDomains, and even to threads in other processes.
NLog.Extension.Logging 版本。 1.0 能够捕获使用 ILogger.BeginScope 创建的上下文属性。这些可以使用 NLog ${mdlc}
.
提取
Microsoft 引擎将默认注入 RequestId
、RequestPath
等属性
如果您使用的是 Application Insights,它们会自动将 System.Diagnostics.Activity.Current
设置为一个对象,该对象具有您可能需要的所有 Application Insights 信息以及更多信息,包括 RootId
和 Id
让你与其他事件相关联。
有关更多详细信息以及如何使用 nlog 轻松记录它,请参阅 this answer。
我正在使用 WebAPI 构建一个 API,并且一直在使用 NLog 在整个堆栈中进行日志记录。我的 API 解决方案有两个主要项目,包括:
- 实现控制器和 webapi 东西的网站层本身
- 以类似 CQRS 的方式实现 "async" 命令和处理程序的服务层
我想要实现的是自动生成一个唯一的 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,并且数据将毫无用处。
因此,为了将所有日志消息关联回 Web 中发起的请求API,我是否应该考虑另一种机制?
看看 LogicalCallContext. As of .NET 4.5, it supports async 个场景。
The .NET Framework has a little-known facility that allows you to associate data with a “logical” thread-of-execution. This facility is called logical call context and it allows data to flow to other threads, AppDomains, and even to threads in other processes.
NLog.Extension.Logging 版本。 1.0 能够捕获使用 ILogger.BeginScope 创建的上下文属性。这些可以使用 NLog ${mdlc}
.
Microsoft 引擎将默认注入 RequestId
、RequestPath
等属性
如果您使用的是 Application Insights,它们会自动将 System.Diagnostics.Activity.Current
设置为一个对象,该对象具有您可能需要的所有 Application Insights 信息以及更多信息,包括 RootId
和 Id
让你与其他事件相关联。
有关更多详细信息以及如何使用 nlog 轻松记录它,请参阅 this answer。