Log ASP.NET Core Request and Response using Middleware

12-01-2016
source code

Introduction

In this blog I will create a middleware to log the request and response of an API. Middleware is a piece of code, a class in this example that sets in the request/response pipeline to do specific things. Our middleware will log the request and response that goes in and out of an API call respectively.

Logging Mechanism

To keep thing simple and focused, I am using the built in Logger that is shipped with ASP.NET core. This logger logs information to the console in this example as information.

Log Request Middleware

The log request middleware extracts the request body and convert it to a string. It also logs the request url which will include any query string parameters if they exist. Here is the definition for this middleware.

public class LogRequestMiddleware
{
    private readonly RequestDelegate next;
    private readonly ILogger _logger;
    private Func _defaultFormatter = (state, exception) => state;

    public LogRequestMiddleware(RequestDelegate next, ILogger logger)
    {
        this.next = next;
        _logger = logger;
    }

    public async Task Invoke(HttpContext context)
    {
        var requestBodyStream = new MemoryStream();
        var originalRequestBody = context.Request.Body;

        await context.Request.Body.CopyToAsync(requestBodyStream);
        requestBodyStream.Seek(0, SeekOrigin.Begin);

        var url = UriHelper.GetDisplayUrl(context.Request);
        var requestBodyText = new StreamReader(requestBodyStream).ReadToEnd();
        _logger.Log(LogLevel.Information, 1, $"REQUEST METHOD: {context.Request.Method}, REQUEST BODY: {requestBodyText}, REQUEST URL: {url}", null, _defaultFormatter);

        requestBodyStream.Seek(0, SeekOrigin.Begin);
        context.Request.Body = requestBodyStream;

        await next(context);
        context.Request.Body = originalRequestBody;
    }
}

Few points to notice from this middleware:

  • The context.Request.Body is a MemoryStream that doesn't accept seek that's why we have to copy it to another stream in order to read its content
  • We need to keep rewinding the copied MemoryStream to the beginning after reading it.
  • After the await next(context) call I am assigning the original MemoryStream back. Whatever comes after await next(context) is the outbound i.e. response.

Note: In this middleware you can actually change the request body as well. For example you can add extra information to the request then return the original as we are not changing the original request's body

Log Response Middleware

The log response middleware logs the response's body as string and status code. Here is the full listing for this middleware.

public class LogResponseMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;
    private Func _defaultFormatter = (state, exception) => state;

    public LogResponseMiddleware(RequestDelegate next, ILogger logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task Invoke(HttpContext context)
    {
        var bodyStream = context.Response.Body;

        var responseBodyStream = new MemoryStream();
        context.Response.Body = responseBodyStream;

        await _next(context);

        responseBodyStream.Seek(0, SeekOrigin.Begin);
        var responseBody = new StreamReader(responseBodyStream).ReadToEnd();
        _logger.Log(LogLevel.Information, 1, $"RESPONSE LOG: {responseBody}", null, _defaultFormatter);
        responseBodyStream.Seek(0, SeekOrigin.Begin);
        await responseBodyStream.CopyToAsync(bodyStream);
    }
}

Few points to notice from this middleware:

  • I am reading the response's body and status on the outbound i.e. after await _next(context)
  • I am taking a copy of the original response body and make use of it at the end to copy it back to the response

Conclusion

Middleware is a very handy tool that we have in our disposal in ASP.NET core. In this blog I explained how to write middleware to log request and response for an ASP.NET core API.