oi-metrology/Server/ApiLoggingMiddleware.cs

124 lines
5.1 KiB
C#

using OI.Metrology.Server.Models;
using System.Text;
namespace OI.Metrology.Server;
// You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project
public class ApiLoggingMiddleware
{
private readonly AppSettings _AppSettings;
private readonly RequestDelegate _Next;
public ApiLoggingMiddleware(RequestDelegate next, AppSettings appSettings)
{
_AppSettings = appSettings;
_Next = next;
}
// this is the method called in ASP.NET Core middleware to handle an HTTP request
// the middleware allows you to add code to run before and after an http request is handled
// they are stacked together like a pipeline
public async Task Invoke(HttpContext httpContext)
{
try
{
bool doLogging = false;
// check to see if this is a request path that is enabled for logging
if (!string.IsNullOrWhiteSpace(_AppSettings.ApiLoggingPathPrefixes))
{
// check if the request path begins with any part of pathsToLog
if (_AppSettings.ApiLoggingPathPrefixes.Split(';').Any(p => httpContext.Request.Path.StartsWithSegments(new PathString(p))))
{
if (string.IsNullOrWhiteSpace(_AppSettings.ApiLoggingContentTypes))
{
// if no content type filter is defined, log all content types
doLogging = true;
}
else
{
// if there are content type filters configured, only log is the request begins with one of them
string? contentType = httpContext.Request.ContentType;
doLogging = contentType is not null && _AppSettings.ApiLoggingContentTypes.Split(';').Any(ct => contentType.StartsWith(ct));
}
}
}
// if logging is enabled for this request
if (doLogging)
{
DateTime startTime = DateTime.Now;
// get request body
string requestBodyContent = await ReadRequestBody(httpContext.Request);
// save the original response and stream
Stream originalBodyStream = httpContext.Response.Body;
using MemoryStream responseBody = new();
// replace response stream with our memory stream
httpContext.Response.Body = responseBody;
// call next middleware, this is to process the request so we have a response to inspect
await _Next(httpContext);
// get response body into string
_ = httpContext.Response.Body.Seek(0, SeekOrigin.Begin);
string bodyAsText = await new StreamReader(httpContext.Response.Body).ReadToEndAsync();
// copy memory stream to original response stream
_ = httpContext.Response.Body.Seek(0, SeekOrigin.Begin);
await httpContext.Response.Body.CopyToAsync(originalBodyStream);
// log the request and response
LogRequestAndResponse(startTime, httpContext.Request, requestBodyContent, bodyAsText);
return;
}
}
catch (Exception ex)
{
Console.Error.WriteLine("Error in ApiLoggingMiddleware: " + ex.ToString());
}
// proceed with the http request normally
await _Next(httpContext);
}
private static async Task<string> ReadRequestBody(HttpRequest request)
{
HttpRequestRewindExtensions.EnableBuffering(request);
byte[] buffer = new byte[Convert.ToInt32(request.ContentLength)];
_ = await request.Body.ReadAsync(buffer);
string bodyAsText = Encoding.UTF8.GetString(buffer);
_ = request.Body.Seek(0, SeekOrigin.Begin);
return bodyAsText;
}
private void LogRequestAndResponse(DateTime requestTime, HttpRequest request, string requestBody, string responseBody)
{
int threadId = Environment.CurrentManagedThreadId;
string fileName = $"ApiLog{requestTime:yyyyMMdd_hhmmssttt}_{threadId}.txt";
HttpContext context = request.HttpContext;
if (!Directory.Exists(_AppSettings.ApiLogPath))
_ = Directory.CreateDirectory(_AppSettings.ApiLogPath);
using StreamWriter sw = new(Path.Join(_AppSettings.ApiLogPath, fileName), true);
sw.WriteLine($"Request at {requestTime:yyyy/MM/dd hh:mm:ss.ttt} from {context.Connection.RemoteIpAddress}");
sw.WriteLine($"{request.Method} {request.Path} {request.QueryString}");
sw.WriteLine("Request body:");
sw.WriteLine(requestBody);
sw.WriteLine($"Response at {DateTime.Now:yyyy/MM/dd hh:mm:ss.ttt}");
sw.WriteLine(responseBody);
sw.WriteLine("==========");
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class ApiLoggingMiddlewareExtensions
{
public static IApplicationBuilder UseApiLoggingMiddleware(this IApplicationBuilder builder) => builder.UseMiddleware<ApiLoggingMiddleware>();
}