Собрав информацию из нескольких постов и настроив ее под свои нужды, я нашел способ регистрировать данные тела запроса и ответа как свойства структуры журнала serilog.
Я не нашел способа регистрировать тело запроса и ответа только в одном месте (в методе промежуточного программного обеспечения Invoke
), но я нашел обходной путь. Из-за характера конвейера обработки запросов, вот что я должен был сделать:
Код в Startup.cs
:
app.UseMiddleware<RequestResponseLoggingMiddleware>();
app.UseSerilogRequestLogging(opts => opts.EnrichDiagnosticContext = LogHelper.EnrichFromRequest);
, который я использовал * Класс 1012 * для обогащения свойств запроса, как описано в посте Эндрю Локса .
, когда обработка запроса попадает в промежуточное ПО, в методе I 1012 * промежуточного ПО I Я читаю только данные тела запроса и задаю для этого значения строковое c строковое свойство, которое я добавил в LogHelper
класс. Таким образом, я прочитал и сохранил данные тела запроса в виде строки и могу добавить их как обогащенные, когда метод LogHelper.EnrichFromRequest
вызывается
после чтения данных тела запроса, я копирую указатель на исходный поток тела ответа
await _next(context);
вызывается следующим, context.Response
заполняется, и обработка запроса завершается из метода промежуточного программного обеспечения Invoke
и переходит к LogHelper.EnrichFromRequest
в данный момент LogHelper.EnrichFromRequest
выполняет, читает данные тела ответа и устанавливает их как расширенные, а также ранее сохраненные данные тела запроса и некоторые дополнительные свойства
обработка возвращается к методу промежуточного программного обеспечения Invoke
(сразу после await _next(context);
) и копирование содержимого нового потока памяти (который содержит ответ) в исходный поток,
Ниже приведен код, описанный выше в классах LogHelper.cs
и RequestResponseLoggingMiddleware.cs
:
LogHelper.cs:
public static class LogHelper
{
public static string RequestPayload = "";
public static async void EnrichFromRequest(IDiagnosticContext diagnosticContext, HttpContext httpContext)
{
var request = httpContext.Request;
diagnosticContext.Set("RequestBody", RequestPayload);
string responseBodyPayload = await ReadResponseBody(httpContext.Response);
diagnosticContext.Set("ResponseBody", responseBodyPayload);
// Set all the common properties available for every request
diagnosticContext.Set("Host", request.Host);
diagnosticContext.Set("Protocol", request.Protocol);
diagnosticContext.Set("Scheme", request.Scheme);
// Only set it if available. You're not sending sensitive data in a querystring right?!
if (request.QueryString.HasValue)
{
diagnosticContext.Set("QueryString", request.QueryString.Value);
}
// Set the content-type of the Response at this point
diagnosticContext.Set("ContentType", httpContext.Response.ContentType);
// Retrieve the IEndpointFeature selected for the request
var endpoint = httpContext.GetEndpoint();
if (endpoint is object) // endpoint != null
{
diagnosticContext.Set("EndpointName", endpoint.DisplayName);
}
}
private static async Task<string> ReadResponseBody(HttpResponse response)
{
response.Body.Seek(0, SeekOrigin.Begin);
string responseBody = await new StreamReader(response.Body).ReadToEndAsync();
response.Body.Seek(0, SeekOrigin.Begin);
return $"{responseBody}";
}
}
RequestResponseLoggingMiddleware.cs:
public class RequestResponseLoggingMiddleware
{
private readonly RequestDelegate _next;
public RequestResponseLoggingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
// Read and log request body data
string requestBodyPayload = await ReadRequestBody(context.Request);
LogHelper.RequestPayload = requestBodyPayload;
// Read and log response body data
// Copy a pointer to the original response body stream
var originalResponseBodyStream = context.Response.Body;
// Create a new memory stream...
using (var responseBody = new MemoryStream())
{
// ...and use that for the temporary response body
context.Response.Body = responseBody;
// Continue down the Middleware pipeline, eventually returning to this class
await _next(context);
// Copy the contents of the new memory stream (which contains the response) to the original stream, which is then returned to the client.
await responseBody.CopyToAsync(originalResponseBodyStream);
}
}
private async Task<string> ReadRequestBody(HttpRequest request)
{
HttpRequestRewindExtensions.EnableBuffering(request);
var body = request.Body;
var buffer = new byte[Convert.ToInt32(request.ContentLength)];
await request.Body.ReadAsync(buffer, 0, buffer.Length);
string requestBody = Encoding.UTF8.GetString(buffer);
body.Seek(0, SeekOrigin.Begin);
request.Body = body;
return $"{requestBody}";
}
}