我想保留 .NET Core API 中某些 API 端点中的请求和响应日志。为此,我在 ASP.NET Core 中创建了一个 ActionFilter。但是,在我的代码中,代码可以记录请求,但在记录响应的情况下,我收到错误“流不可读”。如何解决这个问题?我的做法正确吗?
public class LogRequestResponseActionFilter : IAsyncActionFilter
{
private readonly ILogger<LogRequestResponseActionFilter> _logger;
public LogRequestResponseActionFilter(ILogger<LogRequestResponseActionFilter> logger)
{
_logger = logger;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context,
ActionExecutionDelegate next)
{
var requestLog = new StringBuilder();
requestLog.AppendLine("Incoming Request:");
requestLog.AppendLine($"Method: {context.HttpContext.Request.Method}");
requestLog.AppendLine($"Path: {context.HttpContext.Request.Path}");
requestLog.AppendLine($"QueryString: {context.HttpContext.Request.QueryString}");
requestLog.AppendLine($"Headers: {FormatHeaders(context.HttpContext.Request.Headers)}");
requestLog.AppendLine($"Schema: {context.HttpContext.Request.Scheme}");
requestLog.AppendLine($"Host: {context.HttpContext.Request.Host}");
requestLog.AppendLine($"Body: {await ReadBodyFromRequest(context.HttpContext.Request)}");
_logger.LogInformation("{requestLog}", requestLog.ToString());
var resultContext = await next();
if (resultContext.Result != null)
{
var responseBodyText = await new StreamReader(context.HttpContext.Response.Body).ReadToEndAsync();
var responseLog = new StringBuilder();
responseLog.AppendLine("Outgoing Response:");
responseLog.AppendLine($"StatusCode: {context.HttpContext.Response.StatusCode}");
responseLog.AppendLine($"ContentType: {context.HttpContext.Response.ContentType}");
responseLog.AppendLine($"Headers: {FormatHeaders(context.HttpContext.Response.Headers)}");
responseLog.AppendLine($"Body: {responseBodyText}");
_logger.LogInformation("{responseLog}", responseLog.ToString());
}
}
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)
{
request.EnableBuffering();
using var streamReader = new StreamReader(request.Body, leaveOpen: true);
var requestBody = await streamReader.ReadToEndAsync();
request.Body.Position = 0;
return requestBody;
}
}
Response.Body 流不可读或不可查找,它是用于向调用者发送响应的写入流。为了能够在过滤器中读取它,您必须引入一种 hack 并用内存流替换 Body(如这个答案中所示。)
我建议改用内置的 HTTP 日志中间件。
builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.All;
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;
});