在 AWS Cloudwatch 中记录响应正文

问题描述 投票:0回答:1

我正在尝试在 Cloudwatch 日志中记录 http 请求的响应正文。它在本地运行良好,但将 lambda 部署到 AWS 时响应正文始终为空。下面是我的中间件的代码以及出现的相关 Cloudwatch 日志。请注意

ResponseBody
元素为空。

 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;
        });
    });
}

我尝试了几种不同的中间件实现,但到目前为止还没有成功。它们显示请求正文,但响应正文为空。

middleware amazon-cloudwatchlogs aspnetcore-environment
1个回答
0
投票

能够使用下面的代码找到解决方案。显然,在使用之前有必要将response.Body替换为新的MemoryStream。

public async Task InvokeAsync(HttpContext context)
{
    context.Request.EnableBuffering();

    Stream originalRequestBodyStream = context.Request.Body;
    using var requestReader = new StreamReader(originalRequestBodyStream);
    string requestBody = await requestReader.ReadToEndAsync().ConfigureAwait(false);
    context.Request.Body.Seek(0, SeekOrigin.Begin);

    Stream originalResponseBodyStream = context.Response.Body;
    using var responseBody = new MemoryStream();
    context.Response.Body = responseBody;

    await _next(context).ConfigureAwait(false);

    string originalResponseBody = string.Empty;

    if (originalResponseBodyStream.CanRead)
    {
        context.Response.Body.Seek(0, SeekOrigin.Begin);
        using var responseReader = new StreamReader(originalResponseBodyStream);
        originalResponseBody = await responseReader.ReadToEndAsync().ConfigureAwait(false);
    }

    context.Response.Body.Seek(0, SeekOrigin.Begin);
    await responseBody.CopyToAsync(originalResponseBodyStream).ConfigureAwait(false);

    logger.LogInformation("Original Request Body: {origRequestBody}", requestBody);
    logger.LogInformation("Original Response Body: {origResponseBody}", originalResponseBody);
}

此外,还需要检查响应正文流的 CanRead 是否可以在本地工作。

© www.soinside.com 2019 - 2024. All rights reserved.