我正在尝试在 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;
});
});
}
我尝试了几种不同的中间件实现,但到目前为止还没有成功。它们显示请求正文,但响应正文为空。
能够使用下面的代码找到解决方案。显然,在使用之前有必要将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 是否可以在本地工作。