如何在 serilog 的输出 .net 核心中添加 'request body'?
How to add 'request body' in serilog's output .net core?
我有一个基于 .net core 3.1 的网站 API。
我正在使用 SeriLog 库作为记录器。
这是我的 SeriLog 配置。 Serilog 已从 'appsettings.json'.
配置
我需要将 'request body' 参数添加到日志输出(如果存在)。有没有办法配置这个。
另外,我分享我的日志输出。
据我了解,您想将 HttpRequest Body
添加到您的日志中。
像这样的东西应该可以帮助你开始,只要它在一个控制器中,使用 async
方法,如果你不能访问 HttpRequest
你可以用 DI [=14 添加一个=] 在你的启动文件中
// Payload.
string payload = string.Empty;
// Create StreamReader And Starting Reading The Request Body.
using (StreamReader streamReader = new StreamReader(this.Request.Body, Encoding.UTF8, true, 1024, true))
{
// Assign The Stream Content To The Payload Object
payload = await streamReader.ReadToEndAsync();
}
// Check If The Payload Has Something.
if (!string.IsEmptyOrNull(payload))
{
// LOG INFO HERE
}
请检查this thread and this article。要记录请求和响应信息(如:请求体),你可以创建一个中间件,并捕获请求和响应体(因为它们是流,你必须先读取它们,然后使用 Serilogs 方法记录它) .
代码如下:
public class SerilogRequestLogger
{
readonly RequestDelegate _next;
public SerilogRequestLogger(RequestDelegate next)
{
if (next == null) throw new ArgumentNullException(nameof(next));
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
if (httpContext == null) throw new ArgumentNullException(nameof(httpContext));
// Push the user name into the log context so that it is included in all log entries
LogContext.PushProperty("UserName", httpContext.User.Identity.Name);
// Getting the request body is a little tricky because it's a stream
// So, we need to read the stream and then rewind it back to the beginning
string requestBody = "";
HttpRequestRewindExtensions.EnableBuffering(httpContext.Request);
Stream body = httpContext.Request.Body;
byte[] buffer = new byte[Convert.ToInt32(httpContext.Request.ContentLength)];
await httpContext.Request.Body.ReadAsync(buffer, 0, buffer.Length);
requestBody = Encoding.UTF8.GetString(buffer);
body.Seek(0, SeekOrigin.Begin);
httpContext.Request.Body = body;
Log.ForContext("RequestHeaders", httpContext.Request.Headers.ToDictionary(h => h.Key, h => h.Value.ToString()), destructureObjects: true)
.ForContext("RequestBody", requestBody)
.Debug("Request information {RequestMethod} {RequestPath} information", httpContext.Request.Method, httpContext.Request.Path);
Log.Information(string.Format("Request Body: {0} ", requestBody));
// The reponse body is also a stream so we need to:
// - hold a reference to the original response body stream
// - re-point the response body to a new memory stream
// - read the response body after the request is handled into our memory stream
// - copy the response in the memory stream out to the original response stream
using (var responseBodyMemoryStream = new MemoryStream())
{
var originalResponseBodyReference = httpContext.Response.Body;
httpContext.Response.Body = responseBodyMemoryStream;
await _next(httpContext);
httpContext.Response.Body.Seek(0, SeekOrigin.Begin);
var responseBody = await new StreamReader(httpContext.Response.Body).ReadToEndAsync();
httpContext.Response.Body.Seek(0, SeekOrigin.Begin);
Log.ForContext("RequestBody", requestBody)
.ForContext("ResponseBody", responseBody)
.Debug("Response information {RequestMethod} {RequestPath} {statusCode}", httpContext.Request.Method, httpContext.Request.Path, httpContext.Response.StatusCode);
await responseBodyMemoryStream.CopyToAsync(originalResponseBodyReference);
}
}
}
注册中间件:
app.UseMiddleware<SerilogRequestLogger>();
我编写了一个自定义中间件来捕获 HTTP 请求和响应。它与 ASP.NET Core 3.X 兼容,也应该与 2.X 和 .NET 5.0 兼容,尽管我还没有用这些框架版本测试它。
这是我的 git 回购的 link:https://github.com/matthew-daddario/AspNetCoreRequestResponseLogger
相关代码是这样的:
public class RequestResponseLoggerMiddleware
{
private readonly RequestDelegate _next;
private readonly bool _isRequestResponseLoggingEnabled;
public RequestResponseLoggerMiddleware(RequestDelegate next, IConfiguration config)
{
_next = next;
_isRequestResponseLoggingEnabled = config.GetValue<bool>("EnableRequestResponseLogging", false);
}
public async Task InvokeAsync(HttpContext httpContext)
{
// Middleware is enabled only when the EnableRequestResponseLogging config value is set.
if (_isRequestResponseLoggingEnabled)
{
Console.WriteLine($"HTTP request information:\n" +
$"\tMethod: {httpContext.Request.Method}\n" +
$"\tPath: {httpContext.Request.Path}\n" +
$"\tQueryString: {httpContext.Request.QueryString}\n" +
$"\tHeaders: {FormatHeaders(httpContext.Request.Headers)}\n" +
$"\tSchema: {httpContext.Request.Scheme}\n" +
$"\tHost: {httpContext.Request.Host}\n" +
$"\tBody: {await ReadBodyFromRequest(httpContext.Request)}");
// Temporarily replace the HttpResponseStream, which is a write-only stream, with a MemoryStream to capture it's value in-flight.
var originalResponseBody = httpContext.Response.Body;
using var newResponseBody = new MemoryStream();
httpContext.Response.Body = newResponseBody;
// Call the next middleware in the pipeline
await _next(httpContext);
newResponseBody.Seek(0, SeekOrigin.Begin);
var responseBodyText = await new StreamReader(httpContext.Response.Body).ReadToEndAsync();
Console.WriteLine($"HTTP request information:\n" +
$"\tStatusCode: {httpContext.Response.StatusCode}\n" +
$"\tContentType: {httpContext.Response.ContentType}\n" +
$"\tHeaders: {FormatHeaders(httpContext.Response.Headers)}\n" +
$"\tBody: {responseBodyText}");
newResponseBody.Seek(0, SeekOrigin.Begin);
await newResponseBody.CopyToAsync(originalResponseBody);
}
else
{
await _next(httpContext);
}
}
private static string FormatHeaders(IHeaderDictionary headers) => string.Join(", ", headers.Select(kvp => $"{{{kvp.Key}: {string.Join(", ", kvp.Value)}}}"));
private static 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;
}
}
您可以通过
options.EnrichDiagnosticContext
从 ReadBodyFromRequest
方法或 FormatRequest
方法 From matthewd98 实现逻辑
但是你还需要将你的正文添加到模板消息中,因为 Serilog 中默认的模板消息是
HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms
并且它不包含任何 Body
占位符
app.UseSerilogRequestLogging(options =>
{
options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
{
// string body = your logic to get body from httpContext.Request.Body
diagnosticContext.Set("Body", body);
};
options.MessageTemplate = "HTTP {RequestMethod} {RequestPath} {Body} responded {StatusCode} in {Elapsed:0.0000}";
});
@Alexander 的 确实做得很好,但它没有解释如何获得很难正确完成的 body。
所以这是完整的答案
首先,您需要一个新的中间件
public class ResetTheBodyStreamMiddleware
{
private readonly RequestDelegate _next;
public ResetTheBodyStreamMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
// Still enable buffering before anything reads
context.Request.EnableBuffering();
// Call the next delegate/middleware in the pipeline
await _next(context);
// Reset the request body stream position to the start so we can read it
context.Request.Body.Position = 0;
}
}
然后你需要注册中间件然后注册UseSerilogRequestLogging
方法。
app.UseMiddleware<ResetTheBodyStreamMiddleware>();
app.UseSerilogRequestLogging(options =>
options.EnrichDiagnosticContext = async (diagnosticContext, context) =>
{
// Reset the request body stream position to the start so we can read it
context.Request.Body.Position = 0;
// Leave the body open so the next middleware can read it.
using StreamReader reader = new(
context.Request.Body,
encoding: Encoding.UTF8,
detectEncodingFromByteOrderMarks: false);
string body = await reader.ReadToEndAsync();
if (body.Length is 0)
return;
object? obj = JsonSerializer.Deserialize<object>(body);
if (obj is null)
return;
diagnosticContext.Set("Body", obj);
options.MessageTemplate = "HTTP {RequestMethod} {RequestPath} {Body} responded {StatusCode} in {Elapsed:0.0000}";
}
);
我有一个基于 .net core 3.1 的网站 API。
我正在使用 SeriLog 库作为记录器。
这是我的 SeriLog 配置。 Serilog 已从 'appsettings.json'.
配置我需要将 'request body' 参数添加到日志输出(如果存在)。有没有办法配置这个。 另外,我分享我的日志输出。
据我了解,您想将 HttpRequest Body
添加到您的日志中。
像这样的东西应该可以帮助你开始,只要它在一个控制器中,使用 async
方法,如果你不能访问 HttpRequest
你可以用 DI [=14 添加一个=] 在你的启动文件中
// Payload.
string payload = string.Empty;
// Create StreamReader And Starting Reading The Request Body.
using (StreamReader streamReader = new StreamReader(this.Request.Body, Encoding.UTF8, true, 1024, true))
{
// Assign The Stream Content To The Payload Object
payload = await streamReader.ReadToEndAsync();
}
// Check If The Payload Has Something.
if (!string.IsEmptyOrNull(payload))
{
// LOG INFO HERE
}
请检查this thread and this article。要记录请求和响应信息(如:请求体),你可以创建一个中间件,并捕获请求和响应体(因为它们是流,你必须先读取它们,然后使用 Serilogs 方法记录它) .
代码如下:
public class SerilogRequestLogger
{
readonly RequestDelegate _next;
public SerilogRequestLogger(RequestDelegate next)
{
if (next == null) throw new ArgumentNullException(nameof(next));
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
if (httpContext == null) throw new ArgumentNullException(nameof(httpContext));
// Push the user name into the log context so that it is included in all log entries
LogContext.PushProperty("UserName", httpContext.User.Identity.Name);
// Getting the request body is a little tricky because it's a stream
// So, we need to read the stream and then rewind it back to the beginning
string requestBody = "";
HttpRequestRewindExtensions.EnableBuffering(httpContext.Request);
Stream body = httpContext.Request.Body;
byte[] buffer = new byte[Convert.ToInt32(httpContext.Request.ContentLength)];
await httpContext.Request.Body.ReadAsync(buffer, 0, buffer.Length);
requestBody = Encoding.UTF8.GetString(buffer);
body.Seek(0, SeekOrigin.Begin);
httpContext.Request.Body = body;
Log.ForContext("RequestHeaders", httpContext.Request.Headers.ToDictionary(h => h.Key, h => h.Value.ToString()), destructureObjects: true)
.ForContext("RequestBody", requestBody)
.Debug("Request information {RequestMethod} {RequestPath} information", httpContext.Request.Method, httpContext.Request.Path);
Log.Information(string.Format("Request Body: {0} ", requestBody));
// The reponse body is also a stream so we need to:
// - hold a reference to the original response body stream
// - re-point the response body to a new memory stream
// - read the response body after the request is handled into our memory stream
// - copy the response in the memory stream out to the original response stream
using (var responseBodyMemoryStream = new MemoryStream())
{
var originalResponseBodyReference = httpContext.Response.Body;
httpContext.Response.Body = responseBodyMemoryStream;
await _next(httpContext);
httpContext.Response.Body.Seek(0, SeekOrigin.Begin);
var responseBody = await new StreamReader(httpContext.Response.Body).ReadToEndAsync();
httpContext.Response.Body.Seek(0, SeekOrigin.Begin);
Log.ForContext("RequestBody", requestBody)
.ForContext("ResponseBody", responseBody)
.Debug("Response information {RequestMethod} {RequestPath} {statusCode}", httpContext.Request.Method, httpContext.Request.Path, httpContext.Response.StatusCode);
await responseBodyMemoryStream.CopyToAsync(originalResponseBodyReference);
}
}
}
注册中间件:
app.UseMiddleware<SerilogRequestLogger>();
我编写了一个自定义中间件来捕获 HTTP 请求和响应。它与 ASP.NET Core 3.X 兼容,也应该与 2.X 和 .NET 5.0 兼容,尽管我还没有用这些框架版本测试它。
这是我的 git 回购的 link:https://github.com/matthew-daddario/AspNetCoreRequestResponseLogger
相关代码是这样的:
public class RequestResponseLoggerMiddleware
{
private readonly RequestDelegate _next;
private readonly bool _isRequestResponseLoggingEnabled;
public RequestResponseLoggerMiddleware(RequestDelegate next, IConfiguration config)
{
_next = next;
_isRequestResponseLoggingEnabled = config.GetValue<bool>("EnableRequestResponseLogging", false);
}
public async Task InvokeAsync(HttpContext httpContext)
{
// Middleware is enabled only when the EnableRequestResponseLogging config value is set.
if (_isRequestResponseLoggingEnabled)
{
Console.WriteLine($"HTTP request information:\n" +
$"\tMethod: {httpContext.Request.Method}\n" +
$"\tPath: {httpContext.Request.Path}\n" +
$"\tQueryString: {httpContext.Request.QueryString}\n" +
$"\tHeaders: {FormatHeaders(httpContext.Request.Headers)}\n" +
$"\tSchema: {httpContext.Request.Scheme}\n" +
$"\tHost: {httpContext.Request.Host}\n" +
$"\tBody: {await ReadBodyFromRequest(httpContext.Request)}");
// Temporarily replace the HttpResponseStream, which is a write-only stream, with a MemoryStream to capture it's value in-flight.
var originalResponseBody = httpContext.Response.Body;
using var newResponseBody = new MemoryStream();
httpContext.Response.Body = newResponseBody;
// Call the next middleware in the pipeline
await _next(httpContext);
newResponseBody.Seek(0, SeekOrigin.Begin);
var responseBodyText = await new StreamReader(httpContext.Response.Body).ReadToEndAsync();
Console.WriteLine($"HTTP request information:\n" +
$"\tStatusCode: {httpContext.Response.StatusCode}\n" +
$"\tContentType: {httpContext.Response.ContentType}\n" +
$"\tHeaders: {FormatHeaders(httpContext.Response.Headers)}\n" +
$"\tBody: {responseBodyText}");
newResponseBody.Seek(0, SeekOrigin.Begin);
await newResponseBody.CopyToAsync(originalResponseBody);
}
else
{
await _next(httpContext);
}
}
private static string FormatHeaders(IHeaderDictionary headers) => string.Join(", ", headers.Select(kvp => $"{{{kvp.Key}: {string.Join(", ", kvp.Value)}}}"));
private static 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;
}
}
您可以通过
options.EnrichDiagnosticContext
从 ReadBodyFromRequest
方法或 FormatRequest
方法 From matthewd98 实现逻辑
但是你还需要将你的正文添加到模板消息中,因为 Serilog 中默认的模板消息是
HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms
并且它不包含任何 Body
占位符
app.UseSerilogRequestLogging(options =>
{
options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
{
// string body = your logic to get body from httpContext.Request.Body
diagnosticContext.Set("Body", body);
};
options.MessageTemplate = "HTTP {RequestMethod} {RequestPath} {Body} responded {StatusCode} in {Elapsed:0.0000}";
});
@Alexander 的
所以这是完整的答案
首先,您需要一个新的中间件
public class ResetTheBodyStreamMiddleware
{
private readonly RequestDelegate _next;
public ResetTheBodyStreamMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
// Still enable buffering before anything reads
context.Request.EnableBuffering();
// Call the next delegate/middleware in the pipeline
await _next(context);
// Reset the request body stream position to the start so we can read it
context.Request.Body.Position = 0;
}
}
然后你需要注册中间件然后注册UseSerilogRequestLogging
方法。
app.UseMiddleware<ResetTheBodyStreamMiddleware>();
app.UseSerilogRequestLogging(options =>
options.EnrichDiagnosticContext = async (diagnosticContext, context) =>
{
// Reset the request body stream position to the start so we can read it
context.Request.Body.Position = 0;
// Leave the body open so the next middleware can read it.
using StreamReader reader = new(
context.Request.Body,
encoding: Encoding.UTF8,
detectEncodingFromByteOrderMarks: false);
string body = await reader.ReadToEndAsync();
if (body.Length is 0)
return;
object? obj = JsonSerializer.Deserialize<object>(body);
if (obj is null)
return;
diagnosticContext.Set("Body", obj);
options.MessageTemplate = "HTTP {RequestMethod} {RequestPath} {Body} responded {StatusCode} in {Elapsed:0.0000}";
}
);