如何拦截并记录Web API中所有进出HTTP请求?
记录Web API传入与传出HTTP请求的最佳实现方案
一、传入请求:沿用ActionFilter(已有方案补充)
你已经知道用ActionFilter处理传入请求,这里补充核心逻辑:实现IAsyncActionFilter,在OnActionExecutionAsync方法中读取请求的方法、路径、Headers、Body等信息记录日志;待Action执行完成后,再记录响应的状态码、响应内容等。
二、传出请求:用自定义DelegatingHandler拦截HttpClient请求
要拦截你的API向外发起的HTTP请求,最佳方案是自定义HttpClient的DelegatingHandler——这是HttpClient管道的原生扩展点,能在请求发送前、响应返回后插入日志逻辑,完全覆盖你用client.GetAsync这类方法发起的请求。
1. 实现自定义日志Handler
创建继承自DelegatingHandler的类,重写SendAsync方法完成日志记录:
public class RequestLoggingHandler : DelegatingHandler { private readonly ILogger<RequestLoggingHandler> _logger; public RequestLoggingHandler(ILogger<RequestLoggingHandler> logger) { _logger = logger; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // 记录传出请求详情 string requestBody = string.Empty; if (request.Content != null) { // 处理流类型请求体,避免消耗流导致后续无法使用 var contentStream = await request.Content.ReadAsStreamAsync(cancellationToken); var originalPosition = contentStream.Position; using var reader = new StreamReader(contentStream); requestBody = await reader.ReadToEndAsync(cancellationToken); contentStream.Position = originalPosition; } _logger.LogInformation( "传出请求: 方法={Method}, 地址={Uri}, Headers={Headers}, Body={Body}", request.Method.Method, request.RequestUri, string.Join("; ", request.Headers.Select(h => $"{h.Key}: {string.Join(",", h.Value)}")), requestBody ); // 执行请求获取响应 var response = await base.SendAsync(request, cancellationToken); // 记录响应详情 string responseBody = string.Empty; if (response.Content != null) { var contentStream = await response.Content.ReadAsStreamAsync(cancellationToken); var originalPosition = contentStream.Position; using var reader = new StreamReader(contentStream); responseBody = await reader.ReadToEndAsync(cancellationToken); contentStream.Position = originalPosition; } _logger.LogInformation( "响应接收: 状态码={StatusCode}, 原因={Reason}, Headers={Headers}, Body={Body}", (int)response.StatusCode, response.ReasonPhrase, string.Join("; ", response.Headers.Select(h => $"{h.Key}: {string.Join(",", h.Value)}")), responseBody ); return response; } }
2. 注册Handler到HttpClient管道
根据你的需求选择注册方式:
- 全局生效:所有通过
IHttpClientFactory创建的HttpClient都会自动应用这个日志Handler:
// 在Program.cs中注册 builder.Services.AddTransient<RequestLoggingHandler>(); builder.Services.AddHttpClient().AddHttpMessageHandler<RequestLoggingHandler>();
- 针对特定HttpClient生效:如果只需要拦截调用某第三方API的请求,可以给HttpClient命名后注册:
builder.Services.AddTransient<RequestLoggingHandler>(); builder.Services.AddHttpClient("ThirdPartyApiClient") .AddHttpMessageHandler<RequestLoggingHandler>(); // 使用时通过命名获取实例 var client = _httpClientFactory.CreateClient("ThirdPartyApiClient"); var response = await client.GetAsync("/api/my/endpoint/name");
3. 关键注意事项
- 处理大文件或流类型的请求/响应时,必须先复制流再读取,读完后重置流位置,否则后续操作会因为流已耗尽而失败。
- 务必过滤敏感信息:比如Authorization头、请求体里的密码等,避免泄露到日志中。可以在日志前对Headers和Body做清洗处理。
三、关联传入与传出请求日志(可选优化)
为了方便排查问题,建议给每个请求分配唯一的Trace ID,将传入请求和它触发的传出请求日志关联起来:
- 在ActionFilter中获取当前请求的
HttpContext.TraceIdentifier,记录到传入请求日志中。 - 发起传出请求时,将这个Trace ID添加到请求头:
client.DefaultRequestHeaders.Add("X-Trace-Id", HttpContext.TraceIdentifier);
- 在
RequestLoggingHandler中读取这个X-Trace-Id头,写入传出请求日志,这样就能通过Trace ID追踪完整的请求链路。
内容的提问来源于stack exchange,提问作者deepo
相关产品推荐
相关产品推荐

