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

部署AWS Lambda后Cloudwatch无法记录HTTP响应体问题

Lambda部署后HTTP响应体无法记录到CloudWatch日志的问题

本地运行时,HTTP请求的响应体可正常记录到CloudWatch日志,但部署到AWS Lambda后,响应体始终为空。请求体记录功能正常,已尝试多种中间件实现方式均未解决问题,以下是相关代码与日志截图:


LoggingTestMiddleware代码

public class LoggingTestMiddleware
{
    private readonly RequestDelegate _next;
    private readonly RequestResponseLoggerOption _options;
    private readonly IRequestResponseLogger _logger;

    public LoggingTestMiddleware
    (RequestDelegate next, IOptions<RequestResponseLoggerOption> options,
     IRequestResponseLogger logger)
    {
        _next = next;
        _options = options.Value;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext httpContext,
           IRequestResponseLogModelCreator logCreator)
    {
        RequestResponseLogModel log = logCreator.LogModel;
        // Middleware is enabled only when the 
        // EnableRequestResponseLogging config value is set.
        if (_options == null || !_options.IsEnabled)
        {
            await _next(httpContext);
            return;
        }
        log.RequestDateTimeUtc = DateTime.UtcNow;
        HttpRequest request = httpContext.Request;

        /*log*/
        log.LogId = Guid.NewGuid().ToString();
        log.TraceId = httpContext.TraceIdentifier;
        var ip = request.HttpContext.Connection.RemoteIpAddress;
        log.ClientIp = ip == null ? null : ip.ToString();
        log.Node = _options.Name;

        /*request*/
        log.RequestMethod = request.Method;
        log.RequestPath = request.Path;
        log.RequestQuery = request.QueryString.ToString();
        log.RequestQueries = FormatQueries(request.QueryString.ToString());
        log.RequestHeaders = FormatHeaders(request.Headers);
        log.RequestBody = await ReadBodyFromRequest(request);
        log.RequestScheme = request.Scheme;
        log.RequestHost = request.Host.ToString();
        log.RequestContentType = request.ContentType;

        // Temporarily replace the HttpResponseStream, 
        // which is a write-only stream, with a MemoryStream to capture 
        // its value in-flight.
        HttpResponse response = httpContext.Response;
        var originalResponseBody = response.Body;
        using var newResponseBody = new MemoryStream();
        response.Body = newResponseBody;

        // Call the next middleware in the pipeline
        try
        {
            await _next(httpContext);
        }
        catch (Exception exception)
        {
            /*exception: but was not managed at app.UseExceptionHandler() 
              or by any middleware*/
            LogError(log, exception);
        }

        newResponseBody.Seek(0, SeekOrigin.Begin);
        var responseBodyText =
            await new StreamReader(response.Body).ReadToEndAsync();

        (string, bool) test = (Encoding.UTF8.GetString(((MemoryStream)newResponseBody).ToArray()), false);
        var responseBodyTest = test.Item1;
        log.ResponseBody = responseBodyTest;

        newResponseBody.Seek(0, SeekOrigin.Begin);
        await newResponseBody.CopyToAsync(originalResponseBody);

        /*response*/
        log.ResponseContentType = response.ContentType;
        log.ResponseStatus = response.StatusCode.ToString();
        log.ResponseHeaders = FormatHeaders(response.Headers);
        //log.ResponseBody = responseBodyText;
        log.ResponseDateTimeUtc = DateTime.UtcNow;


        /*exception: but was managed at app.UseExceptionHandler() 
          or by any middleware*/
        var contextFeature =
            httpContext.Features.Get<IExceptionHandlerPathFeature>();
        if (contextFeature != null && contextFeature.Error != null)
        {
            Exception exception = contextFeature.Error;
            LogError(log, exception);
        }

        //var jsonString = logCreator.LogString(); /*log json*/
        _logger.Log(logCreator);
    }

    private void LogError(RequestResponseLogModel log, Exception exception)
    {
        log.ExceptionMessage = exception.Message;
        log.ExceptionStackTrace = exception.StackTrace;
    }

    private Dictionary<string, string> FormatHeaders(IHeaderDictionary headers)
    {
        Dictionary<string, string> pairs = new Dictionary<string, string>();
        foreach (var header in headers)
        {
            pairs.Add(header.Key, header.Value);
        }
        return pairs;
    }

    private List<KeyValuePair<string, string>> FormatQueries(string queryString)
    {
        List<KeyValuePair<string, string>> pairs =
             new List<KeyValuePair<string, string>>();
        string key, value;
        foreach (var query in queryString.TrimStart('?').Split("&"))
        {
            var items = query.Split("=");
            key = items.Count() >= 1 ? items[0] : string.Empty;
            value = items.Count() >= 2 ? items[1] : string.Empty;
            if (!String.IsNullOrEmpty(key))
            {
                pairs.Add(new KeyValuePair<string, string>(key, value));
            }
        }
        return pairs;
    }

    private async Task<string> ReadBodyFromRequest(HttpRequest request)
    {
        // Ensure the request's body can be read multiple times 
        // (for the next middlewares in the pipeline).
        request.EnableBuffering();
        using var streamReader = new StreamReader(request.Body, leaveOpen: true);
        var requestBody = await streamReader.ReadToEndAsync();
        // Reset the request's body stream position for 
        // next middleware in the pipeline.
        request.Body.Position = 0;
        return requestBody;
    }
}

Startup.cs中的中间件注册代码

public void Configure(IApplicationBuilder app)
{
    app.UseHsts();

    app.Use((context, next) =>
    {
        context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
        return next.Invoke();
    });

    app.UseErrorHandler();

    app.UseXRay(AssemblyName);

    app.UseRouting();

    app.UseOpenApi(settings =>
    {
        settings.Path = "/docs/{documentName}/swagger.json";
    });

    app.UseStaticFiles();

    app.UseSwaggerUi(x =>
    {
        x.Path = "/docs";
        x.DocumentTitle = "Audere API Documentation";
        x.DocumentPath = "/docs/{documentName}/swagger.json";
        x.DocExpansion = "list";
        x.CustomStylesheetPath = "/docs/styles.css";
        x.PersistAuthorization = !this.IsLambda;
        x.TagsSorter = "alpha";
        x.OperationsSorter = "method";
        x.AdditionalSettings["filter"] = true;
        x.AdditionalSettings["deepLinking"] = true;
        x.AdditionalSettings["displayRequestDuration"] = true;
    });

    app.UseApiGatewayAuthorizerAuthentication();

    app.UseAuthorization();

    app.UseLambdaRequestLogging();

    app.UseMiddleware<LoggingTestMiddleware>();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
        endpoints.MapHealthChecks();

        endpoints.MapGet("/", x =>
        {
            x.Response.StatusCode = StatusCodes.Status302Found;
            x.Response.Headers.Location = "/docs/index.html";

            return Task.CompletedTask;
        });
    });
}

CloudWatch日志截图

CloudWatch日志截图:响应体为空


问题解决方法

1. 调整中间件注册顺序

Lambda请求日志中间件(UseLambdaRequestLogging())可能会提前消费响应流,导致自定义日志中间件无法读取。将自定义日志中间件移到它之前:

app.UseAuthorization();

// 先注册自定义日志中间件,再注册Lambda请求日志
app.UseMiddleware<LoggingTestMiddleware>();
app.UseLambdaRequestLogging();

app.UseEndpoints(endpoints => { ... });

2. 修复响应流读取逻辑

代码中混用了response.Body和newResponseBody读取流,统一使用newResponseBody确保读取正确:

// 替换原读取逻辑
newResponseBody.Seek(0, SeekOrigin.Begin);
var responseBodyTest = await new StreamReader(newResponseBody).ReadToEndAsync();
log.ResponseBody = responseBodyTest;

// 重置流位置后复制回原始响应体
newResponseBody.Seek(0, SeekOrigin.Begin);
await newResponseBody.CopyToAsync(originalResponseBody);

3. 验证Lambda角色权限

确认Lambda执行角色拥有完整的CloudWatch Logs写入权限:logs:CreateLogGroup、logs:CreateLogStream、logs:PutLogEvents,避免权限缺失导致字段丢失。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 02:20:58