部署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日志截图

问题解决方法
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
相关产品推荐
相关产品推荐

