You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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 能正常读取请求体。

但原响应日志实现存在两个关键问题:

  1. 直接读取默认的只写响应流导致报错;
  2. 即便能读取流内容,读取后未将内容写回原流,客户端会收不到响应。

修改后的方案解决了上述问题,既保证了请求和响应日志的完整性,也不会影响客户端接收响应。

额外补充两点优化建议:

  • 若业务中存在大体积响应体,使用内存流可能占用过多内存,可考虑用临时文件流替代MemoryStream;
  • 若只需给特定端点添加日志,可通过[ServiceFilter(typeof(LogRequestResponseActionFilter))]特性直接标记目标控制器/Action;若全局注册过滤器,可配合排除特性(如自定义[SkipLogging])跳过不需要记录的端点。

内容的提问来源于stack exchange,提问作者Simant

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 18:15:20