using System.Diagnostics; using System.Text; using Microsoft.Extensions.Configuration; using Serilog; namespace MyNewProjectName.AdminAPI.Middleware; /// /// Middleware to log request and response bodies /// WARNING: This can generate large log files. Use with caution in production. /// Consider enabling only for specific environments or endpoints. /// public class RequestResponseLoggingMiddleware { private readonly RequestDelegate _next; private readonly Serilog.ILogger _logger; private readonly bool _enableRequestLogging; private readonly bool _enableResponseLogging; // Paths that should NOT be logged (e.g., health checks, metrics) private static readonly string[] ExcludedPaths = new[] { "/health", "/metrics", "/favicon.ico" }; public RequestResponseLoggingMiddleware( RequestDelegate next, Serilog.ILogger logger, IConfiguration configuration) { _next = next; _logger = logger; _enableRequestLogging = configuration.GetValue("Logging:EnableRequestLogging", false); _enableResponseLogging = configuration.GetValue("Logging:EnableResponseLogging", false); } public async Task InvokeAsync(HttpContext context) { // Skip logging for excluded paths if (ExcludedPaths.Any(path => context.Request.Path.StartsWithSegments(path))) { await _next(context); return; } var stopwatch = Stopwatch.StartNew(); var requestBody = string.Empty; var responseBody = string.Empty; // Log request if (_enableRequestLogging) { requestBody = await ReadRequestBodyAsync(context.Request); _logger.Information( "Request: {Method} {Path} {QueryString} | Body: {RequestBody}", context.Request.Method, context.Request.Path, context.Request.QueryString, requestBody ); } // Capture response body var originalBodyStream = context.Response.Body; using var responseBodyStream = new MemoryStream(); context.Response.Body = responseBodyStream; try { await _next(context); } finally { stopwatch.Stop(); // Log response if (_enableResponseLogging) { responseBody = await ReadResponseBodyAsync(context.Response); await responseBodyStream.CopyToAsync(originalBodyStream); _logger.Information( "Response: {StatusCode} | Duration: {Duration}ms | Body: {ResponseBody}", context.Response.StatusCode, stopwatch.ElapsedMilliseconds, responseBody ); } else { await responseBodyStream.CopyToAsync(originalBodyStream); } context.Response.Body = originalBodyStream; } } private static async Task ReadRequestBodyAsync(HttpRequest request) { // Enable buffering to allow reading the body multiple times request.EnableBuffering(); using var reader = new StreamReader( request.Body, encoding: Encoding.UTF8, detectEncodingFromByteOrderMarks: false, leaveOpen: true); var body = await reader.ReadToEndAsync(); request.Body.Position = 0; // Reset position for next middleware // Truncate very long bodies to avoid huge logs return body.Length > 10000 ? body[..10000] + "... (truncated)" : body; } private static async Task ReadResponseBodyAsync(HttpResponse response) { response.Body.Seek(0, SeekOrigin.Begin); using var reader = new StreamReader( response.Body, encoding: Encoding.UTF8, detectEncodingFromByteOrderMarks: false, leaveOpen: true); var body = await reader.ReadToEndAsync(); response.Body.Seek(0, SeekOrigin.Begin); // Reset position // Truncate very long bodies to avoid huge logs return body.Length > 10000 ? body[..10000] + "... (truncated)" : body; } }