.NET Core 2.0中如何让WebHost日志携带log4net请求ID属性?
这个问题我之前也踩过坑,核心原因是:Microsoft.AspNetCore.Hosting.Internal.WebHost 内部使用的是Microsoft.Extensions.Logging抽象层,而你目前只给log4net的LogicalThreadContext设置了requestid,两者上下文是独立的,所以WebHost的日志自然拿不到这个属性。要实现全链路日志关联,得把微软日志框架和log4net的上下文打通,同时确保WebHost的日志通过log4net输出。
下面是具体的解决步骤:
1. 确保日志桥接配置正确
首先确认你已经引入了Microsoft.Extensions.Logging.Log4Net.AspNetCore包(这是微软官方推荐的log4net与.NET Core日志系统的桥接包),并在Program.cs中配置WebHost使用log4net作为日志提供器:
public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .ConfigureLogging(logging => { // 清除默认的日志提供器,确保所有日志都走log4net logging.ClearProviders(); logging.AddLog4Net(); // 注册log4net桥接 }) .Build();
2. 修改中间件:同步log4net上下文与微软日志作用域
在你的中间件里,不能只设置log4net的LogicalThreadContext,还要同时开启微软日志的作用域(Scope),这样WebHost的ILogger就能拿到requestid,并且桥接包会自动把作用域中的属性同步到log4net上下文:
public class RequestIdMiddleware { private readonly RequestDelegate _next; private readonly ILogger<RequestIdMiddleware> _logger; public RequestIdMiddleware(RequestDelegate next, ILogger<RequestIdMiddleware> logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { var requestId = Guid.NewGuid().ToString("N"); // 1. 设置log4net的LogicalThreadContext,兼容自定义log4net日志 log4net.LogicalThreadContext.Properties["requestid"] = requestId; // 2. 开启微软日志作用域,让WebHost的ILogger能拿到requestid using (_logger.BeginScope(new Dictionary<string, object> { ["requestid"] = requestId })) { // 可选:把requestId存入HttpContext,方便其他业务代码使用 context.Items["RequestId"] = requestId; // 继续处理请求 await _next(context); } // 清理log4net上下文(异步场景下可选,但养成好习惯避免内存泄漏) log4net.LogicalThreadContext.Properties.Remove("requestid"); } }
别忘了在Startup.cs中注册这个中间件(要尽量靠前,确保所有请求都能被拦截):
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { // 注册RequestId中间件,放在其他中间件之前 app.UseMiddleware<RequestIdMiddleware>(); // 其他中间件配置... if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseMvc(); }
3. 配置log4net布局,输出requestid
最后修改你的log4net配置文件(比如log4net.config),确保布局中包含%property{requestid},这样所有通过log4net输出的日志(包括WebHost的日志)都会带上requestid:
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender"> <layout type="log4net.Layout.PatternLayout"> <!-- 自定义日志格式,加入requestid --> <conversionPattern value="%date [%thread] %-5level %logger - %message | RequestId: %property{requestid}%newline" /> </layout> </appender> <!-- 其他appender配置... --> <root> <level value="INFO" /> <appender-ref ref="ConsoleAppender" /> </root>
原理说明
- 微软的
BeginScope会创建一个异步安全的日志上下文,WebHost内部的ILogger在输出日志时会自动带上作用域中的属性。 Microsoft.Extensions.Logging.Log4Net.AspNetCore桥接包会把微软日志作用域中的属性自动同步到log4net的LogicalThreadContext.Properties中,同时也会读取你手动设置的log4net属性,确保两边的上下文一致。
这样配置完成后,不管是你自己业务代码的log4net日志,还是WebHost生成的日志,都会包含同一个requestid,实现全请求日志关联。
内容的提问来源于stack exchange,提问作者ecif

