.NET Core logging request and responses
Implementing middleware and handling each request before consumed in controller is the best way to do it.
Middlewares executions are the previous step of execution of the application code. We will append this HttpContextLogger between Request and ApiController’s now.
public class HttpContextLogger
{
private readonly RequestDelegate _next;
public HttpContextLogger(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
await _next(context);
}
}
This is a raw class that pass request to the ApiController. First we can wrap the _next() method with Try Catch block to handle all exceptions in this common class.
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
string errorMessage = ex.Message;
/ log to db
}
}
Basically we have just wrapped _next with try catch and handled exception.
And the next one is handling request message,
public async Task Invoke(HttpContext context)
{
string requestMessage = await FormatRequest(context.Request);
try
{
await _next(context);
}
catch (Exception ex)
{
string errorMessage = ex.Message;
/ log to db
}
}
We just need to read Request object inside HttpContext, so we can write a FormatRequest method to do it,
private async Task<string> FormatRequest(HttpRequest request)
{
var body = request.Body;
var buffer = new byte[Convert.ToInt32(request.ContentLength)];
// reading body stream into buffer exact length of request
await request.Body.ReadAsync(buffer, 0, buffer.Length);
// get string from buffer with UTF8 encoding.
var bodyAsText = Encoding.UTF8.GetString(buffer);
// assign temp body object to actual body again to prevent loss.
request.Body = body;
return $"{bodyAsText.Trim()}";
}
Now we have the Request object too.
Lastly we need the response message, We will copy the Stream’s data into our MemoryStream in temp and then we will get response message with FormatResponse Method,
public async Task Invoke(HttpContext context)
{
string requestMessage = await FormatRequest(context.Request);
try
{
// we have to keep the original stream to keep default generated message
var originalBodyStream = context.Response.Body;
// generate a stream to copy response body's stream
await using var memoryStream = new MemoryStream();
// assign our stream to body
context.Response.Body = memoryStream;
await _next(context);
// now we have the response message, We have read it from Stream
// that we defined in this block
string response = await FormatResponse(context.Response);
// set the default body stream to body again.
context.Response.Body = originalBodyStream;
// write the api response to the default body again!
await context.Response.Body.WriteAsync(memoryStream.ToArray());
}
catch (Exception ex)
{
string errorMessage = ex.Message;
/ log to db
}
}
private async Task<string> FormatResponse(HttpResponse response)
{
// put stream position to begining
response.Body.Seek(0, SeekOrigin.Begin);
// read stream to end
var text = await new StreamReader(response.Body).ReadToEndAsync();
// but body stream to beginning again
response.Body.Seek(0, SeekOrigin.Begin);
return $"{text.Trim()}";
}
You can use IoC to call your db layer to write these request, response also!
Here is the full code of HttpContextLogger.cs
public class HttpContextLogger
{
private readonly RequestDelegate _next;
public HttpContextLogger(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context) //, IContextLoggerRepository repo
{
string requestMessage = await FormatRequest(context.Request);
try
{
var originalBodyStream = context.Response.Body;
await using var memoryStream = new MemoryStream();
context.Response.Body = memoryStream;
await _next(context);
string response = await FormatResponse(context.Response);
context.Response.Body = originalBodyStream;
await context.Response.Body.WriteAsync(memoryStream.ToArray());
}
catch (Exception ex)
{
string errorMessage = ex.Message;
// log to db
}
}
private async Task<string> FormatRequest(HttpRequest request)
{
var body = request.Body;
var buffer = new byte[Convert.ToInt32(request.ContentLength)];
await request.Body.ReadAsync(buffer, 0, buffer.Length);
var bodyAsText = Encoding.UTF8.GetString(buffer);
request.Body = body;
return $"{bodyAsText.Trim()}";
}
private async Task<string> FormatResponse(HttpResponse response)
{
response.Body.Seek(0, SeekOrigin.Begin);
var text = await new StreamReader(response.Body).ReadToEndAsync();
response.Body.Seek(0, SeekOrigin.Begin);
return $"{text.Trim()}";
}
}