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 aMemoryStream
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 originalMemoryStream
back. Whatever comes afterawait 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.