Asp.net-Core

Serilog 記錄 web-api 方法,在中間件中添加上下文屬性

  • June 18, 2021

我一直在努力使用 serilog 記錄響應正文有效負載數據,從中間件記錄。我正在開發WEB API Core應用程序,將swagger添加到端點,我的目標是使用serilog(請求和響應數據)將每個端點呼叫記錄到*.json*文件中。

對於GET請求,應記錄響應的主體(作為屬性添加到 serilog 上下文中),對於 POST 請求,應記錄請求和響應的主體。我創建了中間件並設法從請求和響應流中正確檢索數據,並將其作為字元串獲取,但只有*“RequestBody”*被正確記錄。

調試時,我可以看到讀取請求/響應正文工作正常。

以下是 Program->Main 方法的程式碼摘錄:

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”*沒有任何內容(甚至沒有作為屬性添加)感謝任何幫助。

在從幾個文章中收集資訊並根據我的需要對其進行定制之後,我找到了一種將請求和響應主體數據記錄為 serilog 日誌結構屬性的方法。

我沒有找到一種方法將請求和響應主體都記錄在一個地方(在Invoke中間件的方法中),但我找到了一種解決方法。由於請求處理管道的性質,這是我必須做的:

中的程式碼Startup.cs

app.UseMiddleware<RequestResponseLoggingMiddleware>();
app.UseSerilogRequestLogging(opts => opts.EnrichDiagnosticContext = LogHelper.EnrichFromRequest);
  • LogHelper正如Andrew Locks 文章中所述,我使用類來豐富請求屬性。
  • 當請求處理到達中間件時,在中間件的Invoke方法中,我只讀取請求正文數據,並將此值設置為我添加到LogHelper類中的靜態字元串屬性。這樣,我已將請求正文數據讀取並儲存為字元串,並且可以LogHelper.EnrichFromRequest在呼叫方法時將其添加為豐富器
  • 讀取請求正文數據後,我將指針複製到原始響應正文流
  • await _next(context);接下來被呼叫,context.Response被填充,請求處理從中間件的方法中退出Invoke,然後轉到LogHelper.EnrichFromRequest
  • 此時LogHelper.EnrichFromRequest正在執行,現在讀取響應體數據,並將其設置為豐富器,以及之前儲存的請求體數據和一些附加屬性
  • 處理返回到中間件Invoke方法(緊隨其後await _next(context);),並將新記憶體流(包含響應)的內容複製到原始流,

以下是上面LogHelper.csRequestResponseLoggingMiddleware.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}";
   }
}

引用自:https://stackoverflow.com/questions/60076922