Serilog记录Web-api方法,在中间件内部添加上下文属性 [英] Serilog logging web-api methods, adding context properties inside middleware
问题描述
我一直在努力用serilog记录响应主体有效负载数据,并从中间件记录日志.我正在使用 WEB API Core 应用程序,并将 swagger 添加到端点,并且我的目标是将每个端点调用记录到 .json serilog 的文件(请求和响应数据).
I've been struggling to log response body payload data with serilog, logging from middleware. I'm working on WEB API Core application, with swagger added to endpoints, and my goal is to log every endpoint call to a .json file with serilog (both request and response data).
对于 GET 请求,应记录响应的主体(作为属性添加到serilog上下文中),对于POST请求,应记录请求和响应的主体.我已经创建了中间件,并设法从请求和响应流中正确检索数据,并将其作为字符串获取,但是只有"RequestBody" 被正确记录.
For GET requests, body of the response should be logged (added to serilog context as a property), and for POST requests, both body of request and response should be logged. I have created middleware and managed to properly retrieve data from request and response stream, and getting it as a string, but only the "RequestBody" is being logged properly.
调试时,我可以看到读取请求/响应主体工作正常.
When debugging, I can see that reading request/response body works fine.
以下是程序-> Main方法的代码摘录:
Following is the code excerpt from 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" 则没有任何记录(甚至没有添加为属性)感谢任何帮助.
As I mentioned, "RequestBody" is properly logged to file, but nothing for "ResponseBody" (not even added as a property) Appreciate any help.
推荐答案
从多个帖子中收集信息并根据我的需要对其进行了自定义之后,我找到了一种将请求和响应正文数据都记录为serilog日志结构的属性的方法.
After collecting informations from several posts, and customizing it to my needs, I have found a way to log both request and response body data as properties of serilog log structure.
我没有找到一种只在一个地方记录请求和响应正文的方法(在中间件的 Invoke
方法中),但是我找到了一种解决方法.由于请求处理管道的性质,这是我必须要做的:
I didn't find a way to log both request and response body in one place only (in the Invoke
method of the middleware), but I found a workaround. Because of the nature of the request processing pipeline, here is what I had to do:
Startup.cs 中的
代码:
Code in the Startup.cs
:
app.UseMiddleware<RequestResponseLoggingMiddleware>();
app.UseSerilogRequestLogging(opts => opts.EnrichDiagnosticContext = LogHelper.EnrichFromRequest);
-
I have used
LogHelper
class for enriching request properties, just as described in the Andrew Locks post.当请求处理命中中间件时,在中间件的
Invoke
方法中,我正在读取仅请求正文数据,并将此值设置为静态字符串属性,我已添加到LogHelper
类.这样,我已将请求正文数据读取并存储为字符串,并且可以在调用LogHelper.EnrichFromRequest
方法when request processing hits the middleware, in the middleware's
Invoke
method I am reading only request body data, and setting this value to a static string property that I've added toLogHelper
class. This way I have read and stored request body data as string, and can add it as enricher whenLogHelper.EnrichFromRequest
method gets called在读取请求主体数据之后,我正在将指针复制到原始响应主体流
after reading request body data, I am copying a pointer to the original response body stream
等待_next(context);
接下来被调用,context.Response
被填充,并且请求处理从中间件的Invoke
方法,然后转到LogHelper.EnrichFromRequest
await _next(context);
gets called next,context.Response
is populated, and request processing exits out from middleware'sInvoke
method, and goes toLogHelper.EnrichFromRequest
此时正在执行
LogHelper.EnrichFromRequest
,现在读取响应正文数据,并将其设置为richer,以及先前存储的请求正文数据和一些其他属性at this moment
LogHelper.EnrichFromRequest
is executing, reading response body data now, and setting it as enricher, as well as previously stored request body data and some additional properties处理返回到中间件
Invoke
方法(在await _next(context);
之后),然后复制新内存流的内容(其中包含响应)到原始流processing returns to middleware
Invoke
method (right afterawait _next(context);
), and copying the contents of the new memory stream (which contains the response) to the original stream,以下是上面在
LogHelper.cs
和RequestResponseLoggingMiddleware.cs
类中描述的代码:Following is the code described above in
LogHelper.cs
andRequestResponseLoggingMiddleware.cs
classes:LogHelper.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:
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}"; } }
这篇关于Serilog记录Web-api方法,在中间件内部添加上下文属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!