refactor(logging): correlation scope + redaction on shared ILogRedactor seam
Move the per-request correlation context and secret redaction off the MEL mechanism onto the Serilog primitives the shared bootstrap consumes. - GatewayRequestLoggingMiddlewareExtensions now pushes the correlation properties (SessionId / WorkerProcessId / CorrelationId / CommandMethod / ClientIdentity) via Serilog LogContext.PushProperty for the request lifetime and pops them on completion, replacing MEL ILogger.BeginScope. Header parsing and property names are unchanged; GatewayLogScope remains the data holder. - Add GatewayLogRedactorAdapter : ILogRedactor delegating to the existing GatewayLogRedactor policy (mxgw_ bearer tokens / credential-bearing command values), registered as a singleton so the shared RedactionEnricher masks secrets on every event. Remove the now-dead GatewayLoggerExtensions MEL helper. - Tests: add GatewayLogRedactorAdapterTests; serialize the four host-building test classes into one non-parallel collection (HostBuildingCollection) so the process-wide Serilog bootstrap logger is not frozen by two concurrent host builds racing in parallel collections. The net48/x86 worker is untouched.
This commit is contained in:
+48
-7
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Serilog.Context;
|
||||
|
||||
namespace ZB.MOM.WW.MxGateway.Server.Diagnostics;
|
||||
|
||||
@@ -17,7 +18,12 @@ public static class GatewayRequestLoggingMiddlewareExtensions
|
||||
/// <summary>Header name for the command method name.</summary>
|
||||
public const string CommandMethodHeaderName = "x-command-method";
|
||||
|
||||
/// <summary>Adds gateway request logging scope middleware that reads correlation headers and redacts sensitive data.</summary>
|
||||
/// <summary>
|
||||
/// Adds gateway request logging middleware that reads the correlation headers and pushes them
|
||||
/// as Serilog <see cref="LogContext"/> properties for the duration of the request. The pushed
|
||||
/// properties (SessionId / WorkerProcessId / CorrelationId / CommandMethod / ClientIdentity)
|
||||
/// are disposed when the request completes; the shared redaction enricher masks any secrets.
|
||||
/// </summary>
|
||||
/// <param name="app">Application builder.</param>
|
||||
public static IApplicationBuilder UseGatewayRequestLoggingScope(this IApplicationBuilder app)
|
||||
{
|
||||
@@ -25,21 +31,56 @@ public static class GatewayRequestLoggingMiddlewareExtensions
|
||||
|
||||
return app.Use(async (context, next) =>
|
||||
{
|
||||
ILogger logger = context.RequestServices
|
||||
.GetRequiredService<ILoggerFactory>()
|
||||
.CreateLogger("MxGateway.Request");
|
||||
|
||||
using IDisposable? scope = logger.BeginGatewayScope(new GatewayLogScope(
|
||||
GatewayLogScope scope = new(
|
||||
SessionId: ReadHeader(context, SessionIdHeaderName),
|
||||
WorkerProcessId: ReadInt32Header(context, WorkerProcessIdHeaderName),
|
||||
CorrelationId: ReadUInt64Header(context, CorrelationIdHeaderName),
|
||||
CommandMethod: ReadHeader(context, CommandMethodHeaderName),
|
||||
ClientIdentity: ReadHeader(context, "authorization")));
|
||||
ClientIdentity: ReadHeader(context, "authorization"));
|
||||
|
||||
using IDisposable correlationScope = PushCorrelationProperties(scope);
|
||||
|
||||
await next(context);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes the populated <paramref name="scope"/> properties onto the Serilog
|
||||
/// <see cref="LogContext"/>, returning a single disposable that pops them all when the request
|
||||
/// completes. Only the properties present in <see cref="GatewayLogScope.ToDictionary"/> (which
|
||||
/// already applies the client-identity redaction policy) are pushed.
|
||||
/// </summary>
|
||||
/// <param name="scope">The correlation properties for the current request.</param>
|
||||
/// <returns>A disposable that removes the pushed properties on disposal.</returns>
|
||||
private static IDisposable PushCorrelationProperties(GatewayLogScope scope)
|
||||
{
|
||||
Stack<IDisposable> pushed = new();
|
||||
|
||||
foreach (KeyValuePair<string, object?> property in scope.ToDictionary())
|
||||
{
|
||||
pushed.Push(LogContext.PushProperty(property.Key, property.Value));
|
||||
}
|
||||
|
||||
return new CorrelationPropertyScope(pushed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the pushed <see cref="LogContext"/> property bindings in reverse order, restoring
|
||||
/// the ambient context to its pre-request state.
|
||||
/// </summary>
|
||||
private sealed class CorrelationPropertyScope(Stack<IDisposable> bindings) : IDisposable
|
||||
{
|
||||
private readonly Stack<IDisposable> _bindings = bindings;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
while (_bindings.Count > 0)
|
||||
{
|
||||
_bindings.Pop().Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string? ReadHeader(HttpContext context, string headerName)
|
||||
{
|
||||
return context.Request.Headers.TryGetValue(headerName, out StringValues values)
|
||||
|
||||
Reference in New Issue
Block a user