feat(audit): M5.3 response-capture increments — request headers, ceiling-hits counter, per-method body opt-out (T7)
1. Request headers in Extra JSON (AuditWriteMiddleware): adds a `requestHeaders` object to the existing Extra JSON alongside remoteIp/userAgent; headers whose names appear in AuditLogOptions.HeaderRedactList (Authorization, X-Api-Key, Cookie, Set-Cookie by default) are replaced with "<redacted>" using OrdinalIgnoreCase matching — same policy as ScadaBridgeAuditRedactor. 2. AuditInboundCeilingHits counter: new IAuditInboundCeilingHitsCounter interface + NoOpAuditInboundCeilingHitsCounter default; AuditCentralHealthSnapshot implements the interface (Interlocked field, thread-safe) and exposes AuditInboundCeilingHits on IAuditCentralHealthSnapshot; AddAuditLog registers the NoOp default, AddAuditLogCentralMaintenance forwards to the snapshot; AuditWriteMiddleware accepts the counter as an optional ctor arg and increments it once per request where either the request or response body hit the cap. 3. Per-method SkipBodyCapture opt-out: adds SkipBodyCapture bool to PerTargetRedactionOverride; AuditWriteMiddleware consults the per-target override map at the start of InvokeAsync (before EnableBuffering) and, when set, skips body read + capture entirely — the audit row still emits with headers/metadata but null RequestSummary/ResponseSummary; truncation flags are also cleared so the ceiling-hits counter is not bumped for opted-out methods.
This commit is contained in:
@@ -39,10 +39,12 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Central;
|
||||
public sealed class AuditCentralHealthSnapshot
|
||||
: IAuditCentralHealthSnapshot,
|
||||
ICentralAuditWriteFailureCounter,
|
||||
IAuditRedactionFailureCounter
|
||||
IAuditRedactionFailureCounter,
|
||||
IAuditInboundCeilingHitsCounter
|
||||
{
|
||||
private int _centralAuditWriteFailures;
|
||||
private int _auditRedactionFailure;
|
||||
private int _auditInboundCeilingHits;
|
||||
private readonly ConcurrentDictionary<string, bool> _stalled = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -53,6 +55,10 @@ public sealed class AuditCentralHealthSnapshot
|
||||
public int AuditRedactionFailure =>
|
||||
Interlocked.CompareExchange(ref _auditRedactionFailure, 0, 0);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int AuditInboundCeilingHits =>
|
||||
Interlocked.CompareExchange(ref _auditInboundCeilingHits, 0, 0);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IReadOnlyDictionary<string, bool> SiteAuditTelemetryStalled =>
|
||||
new Dictionary<string, bool>(_stalled);
|
||||
@@ -78,4 +84,8 @@ public sealed class AuditCentralHealthSnapshot
|
||||
/// <inheritdoc/>
|
||||
void IAuditRedactionFailureCounter.Increment() =>
|
||||
Interlocked.Increment(ref _auditRedactionFailure);
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IAuditInboundCeilingHitsCounter.Increment() =>
|
||||
Interlocked.Increment(ref _auditInboundCeilingHits);
|
||||
}
|
||||
|
||||
@@ -50,6 +50,17 @@ public interface IAuditCentralHealthSnapshot
|
||||
/// </summary>
|
||||
int AuditRedactionFailure { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Count of inbound request/response body truncations at the
|
||||
/// <see cref="ZB.MOM.WW.ScadaBridge.AuditLog.Configuration.AuditLogOptions.InboundMaxBytes"/>
|
||||
/// ceiling since process start. Incremented by
|
||||
/// <see cref="ZB.MOM.WW.ScadaBridge.InboundAPI.Middleware.AuditWriteMiddleware"/>
|
||||
/// whenever either the request or response body exceeds the cap and is
|
||||
/// truncated in the audit copy. A sustained non-zero count can indicate
|
||||
/// callers sending unexpectedly large bodies.
|
||||
/// </summary>
|
||||
int AuditInboundCeilingHits { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Per-site latched stalled state: <c>true</c> when the
|
||||
/// <see cref="SiteAuditReconciliationActor"/> has observed two
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Central;
|
||||
|
||||
/// <summary>
|
||||
/// Audit Log (#23) M5.3 (T7) counter sink incremented by
|
||||
/// <see cref="ZB.MOM.WW.ScadaBridge.InboundAPI.Middleware.AuditWriteMiddleware"/>
|
||||
/// whenever an inbound request or response body is truncated at the
|
||||
/// <see cref="ZB.MOM.WW.ScadaBridge.AuditLog.Configuration.AuditLogOptions.InboundMaxBytes"/>
|
||||
/// ceiling. Mirrors the <see cref="ICentralAuditWriteFailureCounter"/> shape:
|
||||
/// one-method, NoOp default, must-never-abort-the-user-facing-action invariant.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A ceiling hit is a normal operational event (the caller sent a large
|
||||
/// body) rather than a failure, but surfacing a cumulative count lets
|
||||
/// operators detect over-size callers early. The
|
||||
/// <see cref="AuditCentralHealthSnapshot"/> production implementation
|
||||
/// accumulates the count via an <c>Interlocked</c> field alongside
|
||||
/// <see cref="ICentralAuditWriteFailureCounter"/> and
|
||||
/// <see cref="ZB.MOM.WW.ScadaBridge.AuditLog.Payload.IAuditRedactionFailureCounter"/>.
|
||||
/// </remarks>
|
||||
public interface IAuditInboundCeilingHitsCounter
|
||||
{
|
||||
/// <summary>Increment the inbound body-ceiling hit counter by one.</summary>
|
||||
void Increment();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Central;
|
||||
|
||||
/// <summary>
|
||||
/// Default <see cref="IAuditInboundCeilingHitsCounter"/> binding used when
|
||||
/// the central health snapshot is not wired (e.g. site composition roots,
|
||||
/// test harnesses that have no health dashboard). All increments are silently
|
||||
/// dropped — correct for environments that have no audit KPI surface.
|
||||
/// </summary>
|
||||
public sealed class NoOpAuditInboundCeilingHitsCounter : IAuditInboundCeilingHitsCounter
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public void Increment() { }
|
||||
}
|
||||
Reference in New Issue
Block a user