ASP.NET Core中FastEndpoints全场景日志覆盖方案咨询
问题背景
我们有一个已上线的大型ASP.NET Core项目,使用的FastEndpoints组件版本如下:
<PackageReference Include="FastEndpoints" Version="5.20.0" /> <PackageReference Include="FastEndpoints.Swagger" Version="5.20.0" />
项目包含50+基于FastEndpoints开发的端点,当前通过集中式日志中间件执行端点逻辑:
await logger.ExecuteAndLogRequest( request, () => ExecuteRequestAsync(request, cancellationToken), //endpoint method cancellationToken);
存在两个核心问题:
- 该日志逻辑仅在端点返回
EndpointResponse时记录请求信息,而50%的端点使用ThrowError()、SendErrorsAsync()等FastEndpoints原生方法,这类请求无返回值,导致日志缺失。 - 新增
ErrorHandlerLogger中间件捕获4xx及以上状态码请求的日志,但导致正常返回的请求出现日志重复。
需要一套能覆盖所有场景(FastEndpoints验证失败、自定义异常、正常响应)的日志方案,且必须兼容现有已上线的大型项目。
解决方案
方案1:利用FastEndpoints全局事件钩子(推荐)
FastEndpoints 5.x版本提供了全局事件拦截能力,可在端点执行的全生命周期注入日志逻辑,无需修改现有端点代码,侵入性极低,完美兼容现有项目。
实现步骤
- 创建全局事件处理类,实现
IEndpointEvents接口:
public class GlobalEndpointLogger : IEndpointEvents { private readonly IEndpointLogger _logger; public GlobalEndpointLogger(IEndpointLogger logger) { _logger = logger; } // 端点执行前(可选,用于记录请求启动信息) public Task OnBeforeExecuteAsync(EndpointDefinition endpoint, HttpContext ctx, CancellationToken ct) { // 可记录请求路径、HTTP方法、请求体等基础信息 return Task.CompletedTask; } // 端点执行成功(正常返回EndpointResponse时触发) public Task OnAfterExecuteAsync(EndpointDefinition endpoint, HttpContext ctx, object? response, CancellationToken ct) { // 复用原有日志逻辑,保证格式一致性 var request = ctx.RequestServices.GetService(endpoint.RequestType); if (request != null && response is EndpointResponse) { _logger.ExecuteAndLogRequest(request, () => Task.FromResult(response), ct).Wait(ct); } return Task.CompletedTask; } // 端点执行出错(覆盖ThrowError、SendErrorsAsync、验证失败、未捕获异常等所有错误场景) public Task OnErrorAsync(EndpointDefinition endpoint, HttpContext ctx, Exception? ex, CancellationToken ct) { var request = ctx.RequestServices.GetService(endpoint.RequestType); // 获取FastEndpoints原生错误详情 var errorDetails = ctx.Items[ErrorResponseBuilder.ErrorKey] as ErrorResponse; // 执行错误日志记录逻辑,可复用原有日志框架格式 _logger.LogErrorRequest(request, ctx.Response.StatusCode, errorDetails?.Errors, ex); return Task.CompletedTask; } }
- 在
Program.cs中注册全局事件:
builder.Services.AddFastEndpoints(c => { c.EndpointEvents.Add<GlobalEndpointLogger>(); });
- 移除原有日志中间件的
ExecuteAndLogRequest包裹逻辑,以及新增的ErrorHandlerLogger中间件,彻底避免日志重复。
方案2:通过HttpContext标记避免日志重复
如果无法改动全局事件配置,可通过HttpContext.Items标记请求是否已被日志处理,解决重复问题。
实现步骤
- 修改原有
ExecuteAndLogRequest方法,记录日志后标记请求:
public async Task<TResponse> ExecuteAndLogRequest<TRequest, TResponse>(TRequest request, Func<Task<TResponse>> endpointFunc, CancellationToken ct) { var response = await endpointFunc(); // 执行原有正常请求日志逻辑 LogRequest(request, response); // 标记该请求已完成日志记录 HttpContextAccessor.HttpContext.Items["RequestLogged"] = true; return response; }
- 修改
ErrorHandlerLogger中间件,仅处理未标记的错误请求:
public async Task Invoke(HttpContext context, IEndpointLogger logger) { await _next(context); if (context.Response.StatusCode >= 400 && !context.Items.ContainsKey("RequestLogged")) { var endpointDef = context.GetEndpoint()?.Metadata.GetMetadata<EndpointDefinition>(); var request = context.RequestServices.GetService(endpointDef?.RequestType); // 执行错误日志记录逻辑 logger.LogErrorRequest(request, context.Response.StatusCode); } }
- 确保中间件注册顺序:FastEndpoints中间件 ->
ErrorHandlerLogger中间件,保证错误状态码先被FastEndpoints设置完成。
方案3:结合FastEndpoints内置错误响应构建器
利用FastEndpoints原生的ErrorResponseBuilder统一处理验证失败、ThrowError等场景的日志,同时保留正常响应的日志逻辑。
实现步骤
- 在
Program.cs中配置全局错误响应构建器,注入日志:
builder.Services.AddFastEndpoints(c => { c.ErrorResponseBuilder = (failures, ctx, statusCode) => { var logger = ctx.RequestServices.GetRequiredService<IEndpointLogger>(); var endpointDef = ctx.GetEndpoint()?.Metadata.GetMetadata<EndpointDefinition>(); var request = ctx.RequestServices.GetService(endpointDef?.RequestType); // 记录错误请求日志 logger.LogErrorRequest(request, statusCode, failures); return new ErrorResponse { Errors = failures }; }; });
- 保留原有正常响应的日志逻辑,移除重复的
ErrorHandlerLogger中间件。
方案选择建议
- 优先选方案1:完全利用FastEndpoints原生扩展能力,无需修改现有端点代码,侵入性最低,最适合大型已上线项目。
- 若无法改动全局事件配置,选方案2:通过标记避免重复,改动量小,兼容现有逻辑。
- 方案3适合仅需处理FastEndpoints内置错误场景的情况,但需额外处理未捕获的全局异常。
内容的提问来源于stack exchange,提问作者Amv
相关产品推荐
相关产品推荐

