From af08e5de37233873e31b2911c849fd7dd8f0e536 Mon Sep 17 00:00:00 2001 From: Thomas Andersen Date: Wed, 4 Dec 2024 19:12:14 +0100 Subject: [PATCH] Added requestLogger that can be toggled using feature flag (#307) --- .../CoffeeCard.Library/Utils/FeatureFlags.cs | 4 + .../Controllers/v2/MobilePayController.cs | 13 +-- .../Helpers/RequestLoggerMiddleware.cs | 88 +++++++++++++++++++ coffeecard/CoffeeCard.WebApi/Startup.cs | 7 ++ coffeecard/CoffeeCard.WebApi/appsettings.json | 3 +- 5 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 coffeecard/CoffeeCard.WebApi/Helpers/RequestLoggerMiddleware.cs diff --git a/coffeecard/CoffeeCard.Library/Utils/FeatureFlags.cs b/coffeecard/CoffeeCard.Library/Utils/FeatureFlags.cs index f277b138..3d9e2eed 100644 --- a/coffeecard/CoffeeCard.Library/Utils/FeatureFlags.cs +++ b/coffeecard/CoffeeCard.Library/Utils/FeatureFlags.cs @@ -6,5 +6,9 @@ public class FeatureFlags /// Controls whether the API should manage the registration to the MobilePayWebhooks API at startup. Disabling this feature flag, assumes that the Webhook Registration is handled outside of the Analog Core API. /// public const string MobilePayManageWebhookRegistration = "MobilePayManageWebhookRegistration"; + /// + /// Controls whether the RequestLoggerMiddleware is added to the pipeline + /// + public const string RequestLoggerEnabled = "RequestLoggerEnabled"; } } diff --git a/coffeecard/CoffeeCard.WebApi/Controllers/v2/MobilePayController.cs b/coffeecard/CoffeeCard.WebApi/Controllers/v2/MobilePayController.cs index f3373039..038c2403 100644 --- a/coffeecard/CoffeeCard.WebApi/Controllers/v2/MobilePayController.cs +++ b/coffeecard/CoffeeCard.WebApi/Controllers/v2/MobilePayController.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; using Serilog; namespace CoffeeCard.WebApi.Controllers.v2 @@ -25,16 +26,18 @@ public class MobilePayController : ControllerBase private readonly IPurchaseService _purchaseService; private readonly MobilePaySettingsV2 _mobilePaySettings; private readonly IWebhookService _webhookService; + private readonly ILogger _logger; /// /// Initializes a new instance of the class. /// public MobilePayController(IPurchaseService purchaseService, IWebhookService webhookService, - MobilePaySettingsV2 mobilePaySettings) + MobilePaySettingsV2 mobilePaySettings, ILogger logger) { _purchaseService = purchaseService; _webhookService = webhookService; _mobilePaySettings = mobilePaySettings; + _logger = logger; } /// @@ -82,15 +85,15 @@ private async Task VerifySignature(string mpSignatureHeader) rawRequestBody = await stream.ReadToEndAsync(); } - Log.Debug("Raw request body (trimmed): '{Body}'", rawRequestBody.Trim()); - Log.Debug("Endpoint Url '{EndpointUrl}', SignatureKey '{Signature}'", endpointUrl, signatureKey); + _logger.LogDebug("Raw request body (trimmed): '{Body}'", rawRequestBody.Trim()); + _logger.LogDebug("Endpoint Url '{EndpointUrl}', SignatureKey '{Signature}'", endpointUrl, signatureKey); var hash = new HMACSHA1(Encoding.UTF8.GetBytes(signatureKey)) .ComputeHash(Encoding.UTF8.GetBytes(endpointUrl + rawRequestBody.Trim())); var computedSignature = Convert.ToBase64String(hash); - Log.Debug("ComputedSignature: {Signature}", computedSignature); - Log.Debug("mpSignatureHeader {mpSignatureHeader}", mpSignatureHeader); + _logger.LogDebug("ComputedSignature: {Signature}", computedSignature); + _logger.LogDebug("mpSignatureHeader {mpSignatureHeader}", mpSignatureHeader); return mpSignatureHeader.Equals(computedSignature); } diff --git a/coffeecard/CoffeeCard.WebApi/Helpers/RequestLoggerMiddleware.cs b/coffeecard/CoffeeCard.WebApi/Helpers/RequestLoggerMiddleware.cs new file mode 100644 index 00000000..658f6ed8 --- /dev/null +++ b/coffeecard/CoffeeCard.WebApi/Helpers/RequestLoggerMiddleware.cs @@ -0,0 +1,88 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using System.IO; +using System.Text; +using System.Threading.Tasks; + +namespace CoffeeCard.WebApi.Helpers; + +public class RequestLoggerMiddleware +{ + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + public RequestLoggerMiddleware(RequestDelegate next, ILogger logger) + { + _next = next; + _logger = logger; + } + + public async Task InvokeAsync(HttpContext context) + { + // Log the Request + var request = await FormatRequest(context.Request); + _logger.LogDebug("Incoming Request: {Request}", request); + + // Copy original response body stream + var originalResponseBodyStream = context.Response.Body; + + using (var responseBody = new MemoryStream()) + { + context.Response.Body = responseBody; + + // Call the next middleware in the pipeline + await _next(context); + + // Log the Response + var response = await FormatResponse(context.Response); + _logger.LogDebug("Outgoing Response: {Response}", response); + + // Copy the response body back to the original stream + await responseBody.CopyToAsync(originalResponseBodyStream); + } + } + + private async Task FormatRequest(HttpRequest request) + { + request.EnableBuffering(); + + var bodyAsText = string.Empty; + if (request.Body.CanSeek) + { + request.Body.Seek(0, SeekOrigin.Begin); + using (var reader = new StreamReader(request.Body, Encoding.UTF8, leaveOpen: true)) + { + bodyAsText = await reader.ReadToEndAsync(); + } + + request.Body.Seek(0, SeekOrigin.Begin); + } + + var headers = FormatHeaders(request.Headers); + + return + $"Method: {request.Method}, Path: {request.Path}, QueryString: {request.QueryString}, Headers: {headers}, Body: {bodyAsText}"; + } + + private async Task FormatResponse(HttpResponse response) + { + response.Body.Seek(0, SeekOrigin.Begin); + var text = await new StreamReader(response.Body).ReadToEndAsync(); + response.Body.Seek(0, SeekOrigin.Begin); + + return $"StatusCode: {response.StatusCode}, Body: {text}"; + } + + private string FormatHeaders(IHeaderDictionary headers) + { + var formattedHeaders = new StringBuilder(); + + foreach (KeyValuePair header in headers) + { + formattedHeaders.AppendLine($"{header.Key}: {header.Value}"); + } + + return formattedHeaders.ToString().TrimEnd(); + } +} \ No newline at end of file diff --git a/coffeecard/CoffeeCard.WebApi/Startup.cs b/coffeecard/CoffeeCard.WebApi/Startup.cs index d2fd0489..f55605d3 100644 --- a/coffeecard/CoffeeCard.WebApi/Startup.cs +++ b/coffeecard/CoffeeCard.WebApi/Startup.cs @@ -345,6 +345,13 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVers app.UseStaticFiles(); app.UseRouting(); + var featureManager = app.ApplicationServices.GetRequiredService(); + + var isRequestLoggerEnabled = featureManager.IsEnabledAsync(FeatureFlags.RequestLoggerEnabled).Result; + if (isRequestLoggerEnabled) + { + app.UseMiddleware(); + } app.UseCors(); diff --git a/coffeecard/CoffeeCard.WebApi/appsettings.json b/coffeecard/CoffeeCard.WebApi/appsettings.json index a377b255..eb26f4c8 100644 --- a/coffeecard/CoffeeCard.WebApi/appsettings.json +++ b/coffeecard/CoffeeCard.WebApi/appsettings.json @@ -84,6 +84,7 @@ "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId", "WithExceptionDetails" ] }, "FeatureManagement": { - "MobilePayManageWebhookRegistration": false + "MobilePayManageWebhookRegistration": false, + "RequestLoggerEnabled": false } } \ No newline at end of file