如何记录自定义变量?NLog请求时长LayoutRenderer注册异常排查
问题分析与解决方案
你遇到的错误确实和LayoutRenderer的注册逻辑+时机都有关系,而且核心问题是你用全局变量存储请求时长的方式完全不适合多请求场景,同时注册时机可能也没踩对NLog的加载流程。
为什么当前方案不行?
- 线程安全问题:你用一个全局的
requestDuration变量存储每个请求的耗时,在多并发请求下,这个变量会被不同请求覆盖,日志记录的时长完全不准确。 - 注册时机错误:如果你的
LayoutRenderer.Register写在中间件的OnActionExecuted里,NLog在加载配置时(通常是应用启动初期)根本找不到这个自定义Renderer,所以会抛出“LayoutRenderer cannot be found”的错误——NLog只会在初始化时解析配置里的Renderer,后续动态注册的如果没及时让NLog感知到,也会失效。 - 自定义Renderer逻辑错误:你注册的Lambda返回全局变量,而不是当前请求的专属值,本质上不符合每个请求独立记录时长的需求。
正确的实现方式(推荐用NLog内置上下文,无需自定义Renderer)
NLog提供了Mapped Diagnostics Logical Context (MDLC),专门用来存储每个请求的上下文数据,完全适配ASP.NET Core的请求生命周期,而且不需要自定义LayoutRenderer,直接用内置语法即可:
修改中间件代码:
在中间件里用秒表计时,把耗时存入MDLC:public class RequestDurationMiddleware { private readonly RequestDelegate _next; public RequestDurationMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { var stopwatch = Stopwatch.StartNew(); try { await _next(context); } finally { stopwatch.Stop(); // 将请求时长存入MDLC,键名和配置里的对应 NLog.MappedDiagnosticsLogicalContext.Set("RequestDuration", stopwatch.ElapsedMilliseconds); } } } // 中间件扩展方法 public static class RequestDurationMiddlewareExtensions { public static IApplicationBuilder UseRequestDuration(this IApplicationBuilder builder) { return builder.UseMiddleware<RequestDurationMiddleware>(); } }修改nlog.config配置:
直接使用内置的mdlcLayoutRenderer读取上下文里的值:<column name="RequestDuration" layout="${mdlc:item=RequestDuration}" quoting="Nothing"/>确保中间件注册时机:
在Program.cs里把中间件注册在管道的靠前位置(比如UseRouting之后,UseEndpoints之前):var builder = WebApplication.CreateBuilder(args); // 配置NLog(确保在注册中间件之前) builder.Logging.ClearProviders(); builder.Logging.SetMinimumLevel(LogLevel.Trace); builder.Host.UseNLog(); // 注册服务 builder.Services.AddControllersWithViews(); var app = builder.Build(); // 注册中间件 app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); // 加入请求时长中间件 app.UseRequestDuration(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run();
如果一定要自定义LayoutRenderer(不推荐,无必要)
如果你坚持要自己注册LayoutRenderer,需要:
- 在应用启动时注册:比如在Program.cs的NLog初始化之后、加载配置之前注册:
// 注册自定义Renderer LayoutRenderer.Register("requestDuration", logEvent => NLog.MappedDiagnosticsLogicalContext.Get("RequestDuration") ?? 0); // 然后加载NLog配置 builder.Host.UseNLog(); - 同样要依赖MDLC存储请求时长(不能用全局变量),这样自定义Renderer才能从当前请求的上下文里拿到正确的值。
这样修改后,NLog就能正确识别你的请求时长字段,而且每个请求的时长都能独立记录,不会互相干扰。
内容的提问来源于stack exchange,提问作者user5268288
相关产品推荐
相关产品推荐

