Serilog регистрирует методы web-api, добавляя свойства контекста в промежуточное ПО - PullRequest
1 голос
/ 05 февраля 2020

Я изо всех сил пытался записать данные полезной нагрузки тела ответа с помощью serilog, ведя запись из промежуточного программного обеспечения. Я работаю над приложением WEB API Core , с добавлением swagger к конечным точкам, и моя цель - записывать каждый вызов конечной точки в файл . json с serilog (данные запроса и ответа).

Для GET запросов должно быть зарегистрировано тело ответа (добавлено в контекст serilog как свойство), а для запросов POST должно быть зарегистрировано и тело запроса, и ответ. Я создал промежуточное программное обеспечение и сумел правильно извлечь данные из потока запросов и ответов и получить их в виде строки, но только "RequestBody" правильно регистрируется.

При отладке я вижу, что тело запроса / ответа на чтение работает нормально.

Ниже приведен фрагмент кода из Program-> Main method:

Log.Logger = new LoggerConfiguration()
    .ReadFrom.Configuration(configuration)
    .Enrich.FromLogContext()
    .CreateLogger();

и код в промежуточном программном обеспечении:

public async Task Invoke(HttpContext context)
{
    // Read and log request body data
    string requestBodyPayload = await ReadRequestBody(context.Request);

    LogContext.PushProperty("RequestBody", requestBodyPayload);

    // Read and log response body data
    var originalBodyStream = context.Response.Body;
    using (var responseBody = new MemoryStream())
    {
        context.Response.Body = responseBody;
        await _next(context);
        string responseBodyPayload = await ReadResponseBody(context.Response);

        if (!context.Request.Path.ToString().EndsWith("swagger.json") && !context.Request.Path.ToString().EndsWith("index.html"))
        {
            LogContext.PushProperty("ResponseBody", responseBodyPayload);
        }

        await responseBody.CopyToAsync(originalBodyStream);
    }
}

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

private 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}";
}

Как я уже говорил, "RequestBody" правильно зарегистрирован в файл, но ничего для "ResponseBody" (даже не добавлен как свойство) Ценю любую помощь.

1 Ответ

1 голос
/ 06 февраля 2020

Собрав информацию из нескольких постов и настроив ее под свои нужды, я нашел способ регистрировать данные тела запроса и ответа как свойства структуры журнала 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}";
    }
}
...