为NLog自定义LayoutRenderer
长话短说
前文《解剖HttpClientFactory,自由扩展HttpMessageHandler》主要想讲如何扩展HttpMessageHandler, 示例为在每个Http请求中的日志中显示TraceId,
写入日志的代码拷贝自 Extension.Logging 源码, 这个日志源码其实无法 完成我课后的小作业: 将TraceId显示到Nlog的LayoutRenderer上。
本次重新实现一个流畅简单的 LoggingHttpMessageHandler, 并添加到 nlog layoutrenderer。
什么是Layout Renderer?
nlog 日志上显示的特定字段,便于检索和分类。
头脑风暴
先给出自定义Renderer,定义名为eqid的自定义Renderer
# 截取自 nlog.config配置文件 <variable name="format1" value="${date:format=yy/MM/dd HH\:mm\:ss} [${level}].[${logger}].[${threadid}}].[${aspnet-request-url:IncludeScheme=false:IncludeHost=false}].[${eqid}]${newline}${message} ${exception:format=tostring}" /> <target name="bce-request" xsi:type="File" layout="${format1}" fileName="${logDir}/bce-request.log" encoding="utf-8"/>
————————————-1—————————-
Nlog 添加自定义LayOutRenderer, https://github.com/NLog/NLog/wiki/How-to-write-a-custom-layout-renderer
有简单的lambda方式,这里我们采用稍微灵活的自定义类方式:
[LayoutRenderer("eqid")]
public class EqidLayoutRenderer : LayoutRenderer
{
protected override void Append(StringBuilder builder, LogEventInfo logEvent)
{
builder.Append(logEvent.Properties["EventId_Name"].ToString());
}
}
关键点是实现 LayoutRenderer 的抽象方法 Append, 关键参数 logevent是写入Logging时候的附带参数
以上我打算从 logEventInfo的某个property中存储 该Renderer值,所以上如上取值方式。
———————————2———————
配合着,我们在写入日志时, 也在该property 中写入该Renderer的值:
public class AttachTraceIdScopeHttpMessageHandler : DelegatingHandler { private readonly ILogger _logger; public AttachTraceIdScopeHttpMessageHandler(ILogger logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { if (request == null) { throw new ArgumentNullException(nameof(request)); } var stopwatch = Stopwatch.StartNew(); var eventName = request.RequestUri.LocalPath.Split('/').LastOrDefault(); _logger.Log(LogLevel.Information, new EventId(100, eventName), $"Start processing HTTP request {request.RequestUri} {request.Method}"); var response = await base.SendAsync(request, cancellationToken); stopwatch.Stop(); _logger.Log(LogLevel.Information, new EventId(101, eventName), $"End processing HTTP request after {stopwatch.Elapsed.TotalMilliseconds}ms - {response.StatusCode}"); return response; } }
EventId 中的Name属性,最后在 nlog的 EventLogInfo中被认定为 Property[EventId_Name], 所以我们有以上操作。
按照上文方式,添加到 CustomHttpMessageHandlerFilter,并注册为 IHttpMessageHandlerFilter 实现。
——————————-–3–———————
按照文档的要求,尽量早点注册自定义Nlog LayoutRenderer,
因此我在 main函数开始的时候就注册了该Renderer, 注册名称是TraceId
public static void Main(string[] args) { LayoutRenderer.Register<EqidLayoutRenderer>("eqid"); ...... }
最终输出如下:
19/12/07 00:35:42 [Info].[System.Net.Http.HttpClient.bce-request.LogicalHandler].[19}].[].[125aa91f0011426c000000045dea5ea0] Start processing HTTP request http://localhost:5000/v1/eqid/125aa91f0011426c000000045dea5ea0 GET 19/12/07 00:35:44 [Info].[System.Net.Http.HttpClient.bce-request.LogicalHandler].[5}].[].[125aa91f0011426c000000045dea5ea0] End processing HTTP request after 2178.9971ms - OK
ok, 阅读本文,请务必参阅 《解剖HttpClientFactory,自由扩展HttpMessageHandler》思路,解剖HttpClientFactory之后,再结合Nlog LayoutRenderer.