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);
}
}