using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using System; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace OI.Metrology.Archive; // You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project public class ApiLoggingMiddleware { private IConfiguration Config { get; } private readonly RequestDelegate _Next; public ApiLoggingMiddleware(RequestDelegate next, IConfiguration config) { Config = config; _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; string pathsToLog = Config[Constants.ApiLoggingPathPrefixes]; string contentTypesToLog = Config[Constants.ApiLoggingContentTypes]; // check to see if this is a request path that is enabled for logging if (!string.IsNullOrWhiteSpace(pathsToLog)) { // check if the request path begins with any part of pathsToLog if (pathsToLog.Split(';').Any(p => httpContext.Request.Path.StartsWithSegments(new PathString(p)))) { if (!string.IsNullOrWhiteSpace(contentTypesToLog)) { // if there are content type filters configured, only log is the request begins with one of them doLogging = contentTypesToLog.Split(';').Any(ct => httpContext.Request.ContentType.StartsWith(ct)); } else { // if no content type filter is defined, log all content types doLogging = true; } } } // 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 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 logPath = Config[Constants.ApiLogPath]; string fileName = $"ApiLog{requestTime:yyyyMMdd_hhmmssttt}_{threadId}.txt"; HttpContext context = request.HttpContext; if (!Directory.Exists(logPath)) _ = Directory.CreateDirectory(logPath); using StreamWriter sw = new(Path.Join(logPath, 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(); }