102 lines
4.1 KiB
C#
102 lines
4.1 KiB
C#
using System.Collections.Concurrent;
|
|
using ZB.MOM.WW.ScadaBridge.AuditLog.Payload;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Central;
|
|
|
|
/// <summary>
|
|
/// Central singleton implementation of
|
|
/// <see cref="IAuditCentralHealthSnapshot"/>. Owns thread-safe
|
|
/// <see cref="System.Threading.Interlocked"/> counters for
|
|
/// <c>CentralAuditWriteFailures</c> + <c>AuditRedactionFailure</c> and a
|
|
/// per-site latched stalled-state map fed by the
|
|
/// <see cref="SiteAuditTelemetryStalledTracker"/>. Also implements the
|
|
/// writer surfaces (<see cref="ICentralAuditWriteFailureCounter"/> +
|
|
/// <see cref="IAuditRedactionFailureCounter"/>) so a single concrete object
|
|
/// is the source of truth — DI binds those two interfaces to this same
|
|
/// singleton instance on the central composition root.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// <b>Why one type for read + write.</b> The writer interfaces are tiny
|
|
/// (<c>Increment()</c>) and the read surface needs visibility of those
|
|
/// counters anyway — having a single class own both means the
|
|
/// <c>Interlocked</c> field IS the snapshot value, no extra plumbing needed.
|
|
/// Mirrors the
|
|
/// <see cref="ZB.MOM.WW.ScadaBridge.HealthMonitoring.SiteHealthCollector"/> pattern where
|
|
/// the collector both receives and exposes the metric.
|
|
/// </para>
|
|
/// <para>
|
|
/// <b>Stalled-state plumbing.</b> The per-site stalled latch lives directly
|
|
/// on this snapshot. <see cref="SiteAuditTelemetryStalledTracker"/> is the
|
|
/// EventStream subscriber that pushes
|
|
/// <see cref="SiteAuditTelemetryStalledChanged"/> publications in via
|
|
/// <see cref="ApplyStalled"/>. Keeping the dictionary on this type (rather
|
|
/// than reading the tracker on every access) lets the snapshot be constructed
|
|
/// without an <see cref="Akka.Actor.ActorSystem"/> dependency — the tracker
|
|
/// is wired up later from the Akka bootstrap, once the system is built.
|
|
/// </para>
|
|
/// </remarks>
|
|
public sealed class AuditCentralHealthSnapshot
|
|
: IAuditCentralHealthSnapshot,
|
|
ICentralAuditWriteFailureCounter,
|
|
IAuditRedactionFailureCounter,
|
|
IAuditInboundCeilingHitsCounter,
|
|
IAuditPurgeFailureCounter
|
|
{
|
|
private int _centralAuditWriteFailures;
|
|
private int _auditRedactionFailure;
|
|
private int _auditInboundCeilingHits;
|
|
private int _auditPurgeFailures;
|
|
private readonly ConcurrentDictionary<string, bool> _stalled = new();
|
|
|
|
/// <inheritdoc/>
|
|
public int CentralAuditWriteFailures =>
|
|
Interlocked.CompareExchange(ref _centralAuditWriteFailures, 0, 0);
|
|
|
|
/// <inheritdoc/>
|
|
public int AuditRedactionFailure =>
|
|
Interlocked.CompareExchange(ref _auditRedactionFailure, 0, 0);
|
|
|
|
/// <inheritdoc/>
|
|
public int AuditInboundCeilingHits =>
|
|
Interlocked.CompareExchange(ref _auditInboundCeilingHits, 0, 0);
|
|
|
|
/// <inheritdoc/>
|
|
public int PurgeFailures =>
|
|
Interlocked.CompareExchange(ref _auditPurgeFailures, 0, 0);
|
|
|
|
/// <inheritdoc/>
|
|
public IReadOnlyDictionary<string, bool> SiteAuditTelemetryStalled =>
|
|
new Dictionary<string, bool>(_stalled);
|
|
|
|
/// <summary>
|
|
/// Apply a <see cref="SiteAuditTelemetryStalledChanged"/> publication
|
|
/// observed by <see cref="SiteAuditTelemetryStalledTracker"/>. Public
|
|
/// so the tracker (which lives in the same assembly but is constructed
|
|
/// later from the Akka host) can push without a friend reference;
|
|
/// readers should call <see cref="SiteAuditTelemetryStalled"/>.
|
|
/// </summary>
|
|
/// <param name="evt">The event carrying the site ID and new stalled state.</param>
|
|
public void ApplyStalled(SiteAuditTelemetryStalledChanged evt)
|
|
{
|
|
if (evt is null) return;
|
|
_stalled[evt.SiteId] = evt.Stalled;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
void ICentralAuditWriteFailureCounter.Increment() =>
|
|
Interlocked.Increment(ref _centralAuditWriteFailures);
|
|
|
|
/// <inheritdoc/>
|
|
void IAuditRedactionFailureCounter.Increment() =>
|
|
Interlocked.Increment(ref _auditRedactionFailure);
|
|
|
|
/// <inheritdoc/>
|
|
void IAuditInboundCeilingHitsCounter.Increment() =>
|
|
Interlocked.Increment(ref _auditInboundCeilingHits);
|
|
|
|
/// <inheritdoc/>
|
|
void IAuditPurgeFailureCounter.Increment() =>
|
|
Interlocked.Increment(ref _auditPurgeFailures);
|
|
}
|