Recently I started developing an API for the company I work for. After some research we resulted to using NLog as a logging library and it has a Layout Renderer for logging the posted request body, so that's good. But, there is also a need for logging the response, the time it took for the request to process and return (since it will be also consumed by 3rd party vendors and usually the way it goes with some of them is: -I clicked the thing. -hmm no you didn't).
Now, I have read so much these days about Middleware logging and but some posts are dated, some solutions work partially (having an issue with viewing the developer page), somewhere in github I've read that it's bad practice to log the response since it can contain sensitive data. Maybe there is something like telemetry I'm missing?
Thanks for your time and help and sorry for the rant, I'm still pretty burned after the endless reading-testing.
What I have already tried and what the current issue is. The issue with the context.Response.Body is that it is a non-readable, but writeable stream. In order to read it, it must be assigned to another stream, then assign a new readable stream to the .Body, then allow it to continue to the controller, read the returned stream and copy it back to the .Body.
The example middleware class. (Credits to: jarz.net | logging-middleware)
public class LoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<LoggingMiddleware> _logger;
public LoggingMiddleware(RequestDelegate next, ILogger<LoggingMiddleware> logger)
{
_logger = logger;
_next = next;
}
public async Task Invoke(HttpContext context)
{
if (_logger.IsEnabled(LogLevel.Trace))
{
string responseBodyString = string.Empty;
try
{
// Swap the original Response.Body stream with one we can read / seek
Stream originalResponseBody = context.Response.Body;
using MemoryStream replacementResponseBody = new MemoryStream();
context.Response.Body = replacementResponseBody;
await _next(context); // Continue processing (additional middleware, controller, etc.)
// Outbound (after the controller)
replacementResponseBody.Position = 0;
// Copy the response body to the original stream
await replacementResponseBody.CopyToAsync(originalResponseBody).ConfigureAwait(false);
context.Response.Body = originalResponseBody;
if (replacementResponseBody.CanRead)
{
replacementResponseBody.Position = 0;
responseBodyString = new StreamReader(replacementResponseBody, leaveOpen: true).ReadToEndAsync().ConfigureAwait(false).GetAwaiter().GetResult();
replacementResponseBody.Position = 0;
}
}
finally
{
if (responseBodyString.Length > 0)
{
_logger.LogTrace($"{responseBodyString}");
}
}
}
else
await _next(context);
}
}