I want to keep the Log of Requests and Responses in certain API endpoints in .NET Core APIs. For that purpose, I have created an ActionFilter in ASP.NET Core. However, in my code, the code can Log the requests but in case of log responses, I receive an error "Stream was not readable." How to fix the issue? Am I doing it the right way?
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;
}
}
The Response.Body stream is not readable or seekable, it's a write stream for sending responses to the caller. To be able to read it in a filter, you'll have to introduce a hack and replace the Body with a memory stream (as seen in this answer.)
I suggest instead using the built in HTTP Logging middleware.