using System.Diagnostics.Metrics; using ZB.MOM.WW.ScadaBridge.Commons.Observability; namespace ZB.MOM.WW.ScadaBridge.AuditLog.Central.HeadExport; /// /// Health signal of the off-box chain-head export (ScadaBridge#115). Updated by /// on every tick; read by the Host's /// audit-chain-head-export /health/ready check. A sink that stays down used to be a /// log line only, silently stopping the external anchor. /// public sealed class AuditChainHeadExportStatus { private static readonly Meter Meter = new(ScadaBridgeTelemetry.MeterName); private static readonly Counter FailuresCounter = Meter.CreateCounter("scadabridge.audit.chain_head_export.failures", unit: "1", description: "AuditLog chain-head export ticks that failed to read or write the heads (sink offline / unreachable); retried next tick."); private readonly object _gate = new(); private long _totalFailures; private int _consecutiveFailures; private DateTime? _lastSuccessUtc; private DateTime? _lastFailureUtc; private string? _lastError; private DateTime? _startedUtc; /// Whether the export is running at all (false = disabled, nothing to report). public bool Enabled { get; private set; } /// Minutes between export ticks (for the staleness test). public int IntervalMinutes { get; private set; } = 60; /// Total failed ticks since process start. public long TotalFailures { get { lock (_gate) { return _totalFailures; } } } /// Failed ticks since the last success. public int ConsecutiveFailures { get { lock (_gate) { return _consecutiveFailures; } } } /// UTC time of the last successful tick, or null. public DateTime? LastSuccessUtc { get { lock (_gate) { return _lastSuccessUtc; } } } /// UTC time of the last failed tick, or null. public DateTime? LastFailureUtc { get { lock (_gate) { return _lastFailureUtc; } } } /// Message of the last failure (never a secret: the exception message only), or null. public string? LastError { get { lock (_gate) { return _lastError; } } } /// UTC time the export loop started, or null. public DateTime? StartedUtc { get { lock (_gate) { return _startedUtc; } } } /// Marks the export as running. /// Tick interval. /// Start time. public void MarkStarted(int intervalMinutes, DateTime nowUtc) { lock (_gate) { Enabled = true; IntervalMinutes = intervalMinutes; _startedUtc = nowUtc; } } /// Records a successful tick. /// Time of the tick. public void RecordSuccess(DateTime nowUtc) { lock (_gate) { _consecutiveFailures = 0; _lastSuccessUtc = nowUtc; } } /// Records a failed tick and bumps the failure counter metric. /// Time of the tick. /// The failure message. public void RecordFailure(DateTime nowUtc, string error) { lock (_gate) { _totalFailures++; _consecutiveFailures++; _lastFailureUtc = nowUtc; _lastError = error; } FailuresCounter.Add(1); } }