ActionFilter中GetCurrentClassLogger日志需构造调用才生效
问题根因
这个现象是典型的日志组件初始化时序+框架生命周期理解偏差导致的,本地正常部署异常是环境启动速度、资源释放时机差异造成的:
- 你虽然没有用静态Logger字段,但
LogManager.GetCurrentClassLogger()本质是直接访问NLog的全局静态日志管理器,NLog默认采用延迟初始化逻辑:第一次调用日志写入方法时,才会加载日志配置、初始化文件/控制台等输出目标、注册日志级别过滤规则。 - 本地调试时因为调试器附加、热重载、启动速度慢等原因,NLog往往在请求到达前就已经被其他逻辑触发完成初始化,所以不管在哪写日志都正常。
- 生产部署后应用启动速度快,如果你不在构造函数里提前调用一次日志方法,第一次触发NLog初始化的时机就落在了
OnResultExecutionAsync阶段:这个阶段已经处于请求处理的末尾,尤其是如果你的日志写在await next()之后,部分请求级服务、日志缓冲区已经开始进入释放流程,NLog第一次初始化会因为依赖资源不可用直接失败,后续所有日志写入都会被丢弃。 - 当你在构造函数里提前调用一次
_logger.Info(),会在请求处理早期、所有托管资源都正常存活的阶段触发NLog完成全量初始化,后续再写日志就不需要重复初始化,自然能正常输出。
另外你的实现还有一个设计隐患:你让RequestLoggerService继承ActionFilterAttribute,但又通过TypeFilterAttribute创建实例,这种写法会干扰框架对过滤器生命周期的判断,极端场景下会出现实例跨请求复用、状态异常的问题。你希望规避静态Logger、方便单元测试Mock的诉求完全合理,但直接给字段赋值LogManager.GetCurrentClassLogger()的写法本质还是依赖NLog全局静态状态,并没有真正和ASP.NET Core的依赖注入体系对齐。
修复方案
不需要保留构造函数里的预热日志调用,按以下方式改造即可彻底解决问题:
- 调整
RequestLoggerService的实现,不要继承ActionFilterAttribute,直接实现对应过滤器接口,所有依赖通过构造函数注入,完全交给DI容器管理:
public class RequestLoggerService : IAsyncResultFilter { private readonly bool _logHeader; private readonly bool _logBody; private readonly ILogger<RequestLoggerService> _logger; // 直接注入ILogger,不需要手动调用LogManager获取 public RequestLoggerService(bool logBody, bool logHeader, ILogger<RequestLoggerService> logger) { _logHeader = logHeader; _logBody = logBody; _logger = logger; } public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) { // 先执行后续请求管道,注意必须加await var executedContext = await next(); // 你的日志记录逻辑,注意读取请求体前要先启用HttpRequest缓冲 // string requestHeadersText = ...; // string requestBodyText = ...; // _logger.LogInformation("requestHeaders: {Headers}", requestHeadersText); // _logger.LogInformation("requestBody: {Body}", requestBodyText); } }
- 确认Program.cs中NLog的注册顺序正确,在应用构建早期就完成日志提供程序注册,从根源上避免延迟初始化问题:
var builder = WebApplication.CreateBuilder(args); // 优先注册日志提供程序 builder.Logging.ClearProviders(); builder.Host.UseNLog(); // 再注册控制器、其他业务服务 builder.Services.AddControllers(); // ...其他服务注册
- 你现有的
RequestLoggerAttribute不需要做任何修改,TypeFilter会自动从DI容器解析ILogger实例注入,不需要手动传参。单元测试时你可以直接传入Mock的ILogger<RequestLoggerService>实例,完全满足你的测试需求。
内容的提问来源于stack exchange,提问作者cSteusloff
相关产品推荐
相关产品推荐

