.NET Core API请求响应日志问题:修复Stream不可读错误
问题修复与实现评估
错误原因
ASP.NET Core 中Response.Body默认是只写流(WriteOnlyStream),无法直接读取,这就是触发“Stream was not readable”错误的核心原因。此外,即便流支持读取,原响应流在写入完成后位置会停在末尾,若不重置位置,后续客户端也无法正确获取响应内容。
修复方案
需要在执行 Action 前将原响应流替换为可读写的MemoryStream,待 Action 执行完成、读取完响应内容后,再把内存流的内容复制回原响应流,并重置位置确保客户端能正常接收响应。修改后的OnActionExecutionAsync方法如下:
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { // 记录请求逻辑(保留原有正确实现) var requestLog = new StringBuilder(); requestLog.AppendLine("Incoming Request:"); requestLog.AppendLine($"Method: {context.HttpContext.Request.Method}"); requestLog.AppendLine($"Path: {context.HttpContext.Request.Path}"); requestLog.AppendLine($"QueryString: {context.HttpContext.Request.QueryString}"); requestLog.AppendLine($"Headers: {FormatHeaders(context.HttpContext.Request.Headers)}"); requestLog.AppendLine($"Schema: {context.HttpContext.Request.Scheme}"); requestLog.AppendLine($"Host: {context.HttpContext.Request.Host}"); requestLog.AppendLine($"Body: {await ReadBodyFromRequest(context.HttpContext.Request)}"); _logger.LogInformation("{requestLog}", requestLog.ToString()); // 保存原响应流,替换为可读写的内存流 var originalResponseBody = context.HttpContext.Response.Body; using var responseBodyStream = new MemoryStream(); context.HttpContext.Response.Body = responseBodyStream; try { // 执行Action获取结果 var resultContext = await next(); // 构建响应日志 var responseLog = new StringBuilder(); responseLog.AppendLine("Outgoing Response:"); responseLog.AppendLine($"StatusCode: {context.HttpContext.Response.StatusCode}"); responseLog.AppendLine($"ContentType: {context.HttpContext.Response.ContentType}"); responseLog.AppendLine($"Headers: {FormatHeaders(context.HttpContext.Response.Headers)}"); // 重置内存流位置,读取响应内容 responseBodyStream.Seek(0, SeekOrigin.Begin); var responseBodyText = await new StreamReader(responseBodyStream).ReadToEndAsync(); responseLog.AppendLine($"Body: {responseBodyText}"); _logger.LogInformation("{responseLog}", responseLog.ToString()); // 将内存流内容复制回原响应流,确保客户端能收到响应 responseBodyStream.Seek(0, SeekOrigin.Begin); await responseBodyStream.CopyToAsync(originalResponseBody); } finally { // 恢复原响应流 context.HttpContext.Response.Body = originalResponseBody; } }
实现方式评估
你的请求日志实现是正确的:通过EnableBuffering()开启请求流缓冲,读取后重置流位置,保证后续中间件或 Action 能正常读取请求体。
但原响应日志实现存在两个关键问题:
- 直接读取默认的只写响应流导致报错;
- 即便能读取流内容,读取后未将内容写回原流,客户端会收不到响应。
修改后的方案解决了上述问题,既保证了请求和响应日志的完整性,也不会影响客户端接收响应。
额外补充两点优化建议:
- 若业务中存在大体积响应体,使用内存流可能占用过多内存,可考虑用临时文件流替代
MemoryStream; - 若只需给特定端点添加日志,可通过
[ServiceFilter(typeof(LogRequestResponseActionFilter))]特性直接标记目标控制器/Action;若全局注册过滤器,可配合排除特性(如自定义[SkipLogging])跳过不需要记录的端点。
内容的提问来源于stack exchange,提问作者Simant
相关产品推荐
相关产品推荐

