How to log requests and responses in .NET Core APIs?

60 Views Asked by At

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;
    }
}
1

There are 1 best solutions below

0
Calvin Furano On

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.


builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;
});