feat(audit)!: ScadaBridge C3 — swap to canonical ZB.MOM.WW.Audit.AuditEvent across seams/emitters/DTO/redactor wiring; transitional 24-col storage shim (Task 2.5)
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Payload;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
|
||||
using ZB.MOM.WW.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Redaction;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
||||
using IAuditWriter = ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services.IAuditWriter;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site;
|
||||
|
||||
@@ -31,7 +32,7 @@ public sealed class FallbackAuditWriter : IAuditWriter
|
||||
private readonly RingBufferFallback _ring;
|
||||
private readonly IAuditWriteFailureCounter _failureCounter;
|
||||
private readonly ILogger<FallbackAuditWriter> _logger;
|
||||
private readonly IAuditPayloadFilter _filter;
|
||||
private readonly IAuditRedactor _redactor;
|
||||
private readonly SemaphoreSlim _drainGate = new(1, 1);
|
||||
|
||||
/// <summary>
|
||||
@@ -48,26 +49,28 @@ public sealed class FallbackAuditWriter : IAuditWriter
|
||||
/// <param name="ring">Drop-oldest ring buffer used to stash events when the primary fails.</param>
|
||||
/// <param name="failureCounter">Counter incremented on each primary failure for health reporting.</param>
|
||||
/// <param name="logger">Logger for diagnostics.</param>
|
||||
/// <param name="filter">Optional payload filter applied before writing; null means no filtering.</param>
|
||||
/// <param name="redactor">Optional canonical redactor applied before writing; null means the always-safe default.</param>
|
||||
public FallbackAuditWriter(
|
||||
IAuditWriter primary,
|
||||
RingBufferFallback ring,
|
||||
IAuditWriteFailureCounter failureCounter,
|
||||
ILogger<FallbackAuditWriter> logger,
|
||||
IAuditPayloadFilter? filter = null)
|
||||
IAuditRedactor? redactor = null)
|
||||
{
|
||||
_primary = primary ?? throw new ArgumentNullException(nameof(primary));
|
||||
_ring = ring ?? throw new ArgumentNullException(nameof(ring));
|
||||
_failureCounter = failureCounter ?? throw new ArgumentNullException(nameof(failureCounter));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
// AuditLog-008: never default to a null filter — over-redact instead.
|
||||
// SafeDefaultAuditPayloadFilter.Instance performs HTTP header
|
||||
// AuditLog-008: never default to a null redactor — over-redact instead.
|
||||
// C3 (Task 2.5): the canonical IAuditRedactor replaces the legacy
|
||||
// IAuditPayloadFilter. SafeDefaultAuditRedactor performs HTTP header
|
||||
// redaction with the hard-coded sensitive defaults (Authorization,
|
||||
// X-Api-Key, Cookie, Set-Cookie) so a test composition root that
|
||||
// doesn't bind the real options never persists those headers
|
||||
// verbatim. The real DefaultAuditPayloadFilter (truncation + body /
|
||||
// SQL-param redaction) is wired by AddAuditLog and takes precedence.
|
||||
_filter = filter ?? Payload.SafeDefaultAuditPayloadFilter.Instance;
|
||||
// X-Api-Key, Cookie, Set-Cookie) on the DetailsJson summaries so a test
|
||||
// composition root that doesn't bind the real options never persists
|
||||
// those headers verbatim. The full ScadaBridgeAuditRedactor (truncation
|
||||
// + body / SQL-param redaction) is wired by AddAuditLog and takes
|
||||
// precedence.
|
||||
_redactor = redactor ?? SafeDefaultAuditRedactor.Instance;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -75,14 +78,14 @@ public sealed class FallbackAuditWriter : IAuditWriter
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(evt);
|
||||
|
||||
// Filter once, up-front. The filtered event flows BOTH to the primary
|
||||
// Redact once, up-front. The redacted event flows BOTH to the primary
|
||||
// and (on failure) to the ring buffer — so a primary outage that
|
||||
// drains later still hands the SqliteAuditWriter a row that has
|
||||
// already been truncated and redacted. The filter contract is
|
||||
// "MUST NOT throw". AuditLog-008: _filter is now non-null (defaults
|
||||
// to SafeDefaultAuditPayloadFilter so header redaction is always
|
||||
// applied even in composition roots that don't wire the real filter).
|
||||
var filtered = _filter.Apply(evt);
|
||||
// already been truncated and redacted. The redactor contract is
|
||||
// "MUST NOT throw". AuditLog-008: _redactor is now non-null (defaults
|
||||
// to SafeDefaultAuditRedactor so header redaction is always applied
|
||||
// even in composition roots that don't wire the real redactor).
|
||||
var filtered = _redactor.Apply(evt);
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Channels;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
|
||||
using ZB.MOM.WW.Audit;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site;
|
||||
|
||||
|
||||
@@ -2,10 +2,11 @@ using System.Threading.Channels;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
||||
using AuditEvent = ZB.MOM.WW.Audit.AuditEvent;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site;
|
||||
|
||||
@@ -236,14 +237,10 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(evt);
|
||||
|
||||
// Site rows always carry a non-null ForwardState; central rows leave it
|
||||
// null. Force Pending on enqueue so callers can pass a bare AuditEvent
|
||||
// without thinking about site-vs-central provenance.
|
||||
var siteEvt = evt.ForwardState is null
|
||||
? evt with { ForwardState = AuditForwardState.Pending }
|
||||
: evt;
|
||||
|
||||
var pending = new PendingAuditEvent(siteEvt);
|
||||
// C3 transitional shim: the canonical record carries no ForwardState
|
||||
// (a site-storage-only concern). Site rows always start Pending; the
|
||||
// forwarding columns + queries are unchanged from the 24-column schema.
|
||||
var pending = new PendingAuditEvent(evt, AuditForwardState.Pending);
|
||||
|
||||
// CreateBounded(FullMode=Wait) means WriteAsync will await room rather
|
||||
// than throw when full — exactly the hot-path back-pressure semantics
|
||||
@@ -360,13 +357,18 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
|
||||
|
||||
foreach (var pending in batch)
|
||||
{
|
||||
var e = pending.Event;
|
||||
pEventId.Value = e.EventId.ToString();
|
||||
pOccurredAt.Value = e.OccurredAtUtc.ToString("o");
|
||||
pChannel.Value = e.Channel.ToString();
|
||||
pKind.Value = e.Kind.ToString();
|
||||
pCorrelationId.Value = (object?)e.CorrelationId?.ToString() ?? DBNull.Value;
|
||||
pSourceSiteId.Value = (object?)e.SourceSiteId ?? DBNull.Value;
|
||||
// C3 transitional shim: decompose the canonical record into
|
||||
// the typed 24-column values the existing SQLite schema
|
||||
// expects (Channel/Kind/Status + the DetailsJson domain
|
||||
// fields). ForwardState rides alongside the canonical record
|
||||
// (site-storage-only) and is bound from pending.ForwardState.
|
||||
var r = AuditRowProjection.Decompose(pending.Event);
|
||||
pEventId.Value = r.EventId.ToString();
|
||||
pOccurredAt.Value = r.OccurredAtUtc.ToString("o");
|
||||
pChannel.Value = r.Channel.ToString();
|
||||
pKind.Value = r.Kind.ToString();
|
||||
pCorrelationId.Value = (object?)r.CorrelationId?.ToString() ?? DBNull.Value;
|
||||
pSourceSiteId.Value = (object?)r.SourceSiteId ?? DBNull.Value;
|
||||
// SourceNode-stamping: caller-provided value wins (preserves
|
||||
// rows reconciled in from other nodes via the same writer);
|
||||
// otherwise stamp from the local INodeIdentityProvider. The
|
||||
@@ -374,24 +376,24 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
|
||||
// time only. If the provider also returns null (unconfigured
|
||||
// node), the row's SourceNode stays NULL — operators see
|
||||
// "needs config" via the schema, not a magic fallback string.
|
||||
var sourceNode = e.SourceNode ?? _nodeIdentity.NodeName;
|
||||
var sourceNode = r.SourceNode ?? _nodeIdentity.NodeName;
|
||||
pSourceNode.Value = (object?)sourceNode ?? DBNull.Value;
|
||||
pSourceInstanceId.Value = (object?)e.SourceInstanceId ?? DBNull.Value;
|
||||
pSourceScript.Value = (object?)e.SourceScript ?? DBNull.Value;
|
||||
pActor.Value = (object?)e.Actor ?? DBNull.Value;
|
||||
pTarget.Value = (object?)e.Target ?? DBNull.Value;
|
||||
pStatus.Value = e.Status.ToString();
|
||||
pHttpStatus.Value = (object?)e.HttpStatus ?? DBNull.Value;
|
||||
pDurationMs.Value = (object?)e.DurationMs ?? DBNull.Value;
|
||||
pErrorMessage.Value = (object?)e.ErrorMessage ?? DBNull.Value;
|
||||
pErrorDetail.Value = (object?)e.ErrorDetail ?? DBNull.Value;
|
||||
pRequestSummary.Value = (object?)e.RequestSummary ?? DBNull.Value;
|
||||
pResponseSummary.Value = (object?)e.ResponseSummary ?? DBNull.Value;
|
||||
pPayloadTruncated.Value = e.PayloadTruncated ? 1 : 0;
|
||||
pExtra.Value = (object?)e.Extra ?? DBNull.Value;
|
||||
pForwardState.Value = (e.ForwardState ?? AuditForwardState.Pending).ToString();
|
||||
pExecutionId.Value = (object?)e.ExecutionId?.ToString() ?? DBNull.Value;
|
||||
pParentExecutionId.Value = (object?)e.ParentExecutionId?.ToString() ?? DBNull.Value;
|
||||
pSourceInstanceId.Value = (object?)r.SourceInstanceId ?? DBNull.Value;
|
||||
pSourceScript.Value = (object?)r.SourceScript ?? DBNull.Value;
|
||||
pActor.Value = (object?)r.Actor ?? DBNull.Value;
|
||||
pTarget.Value = (object?)r.Target ?? DBNull.Value;
|
||||
pStatus.Value = r.Status.ToString();
|
||||
pHttpStatus.Value = (object?)r.HttpStatus ?? DBNull.Value;
|
||||
pDurationMs.Value = (object?)r.DurationMs ?? DBNull.Value;
|
||||
pErrorMessage.Value = (object?)r.ErrorMessage ?? DBNull.Value;
|
||||
pErrorDetail.Value = (object?)r.ErrorDetail ?? DBNull.Value;
|
||||
pRequestSummary.Value = (object?)r.RequestSummary ?? DBNull.Value;
|
||||
pResponseSummary.Value = (object?)r.ResponseSummary ?? DBNull.Value;
|
||||
pPayloadTruncated.Value = r.PayloadTruncated ? 1 : 0;
|
||||
pExtra.Value = (object?)r.Extra ?? DBNull.Value;
|
||||
pForwardState.Value = pending.ForwardState.ToString();
|
||||
pExecutionId.Value = (object?)r.ExecutionId?.ToString() ?? DBNull.Value;
|
||||
pParentExecutionId.Value = (object?)r.ParentExecutionId?.ToString() ?? DBNull.Value;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -405,7 +407,7 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
|
||||
// recorded under the first writer's payload.
|
||||
_logger.LogDebug(ex,
|
||||
"Duplicate EventId {EventId} swallowed by SqliteAuditWriter",
|
||||
e.EventId);
|
||||
r.EventId);
|
||||
pending.Completion.TrySetResult();
|
||||
}
|
||||
}
|
||||
@@ -788,34 +790,36 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
|
||||
|
||||
private static AuditEvent MapRow(SqliteDataReader reader)
|
||||
{
|
||||
return new AuditEvent
|
||||
{
|
||||
EventId = Guid.Parse(reader.GetString(0)),
|
||||
OccurredAtUtc = DateTime.Parse(reader.GetString(1),
|
||||
// C3 transitional shim: recompose the canonical record from the 24
|
||||
// columns. The ForwardState column (ordinal 20) is read for the
|
||||
// schema's sake but NOT placed on the canonical record — it stays a
|
||||
// site-storage-only concern (the forwarding queries below own it).
|
||||
return AuditRowProjection.Recompose(new AuditRowProjection.AuditRowValues(
|
||||
EventId: Guid.Parse(reader.GetString(0)),
|
||||
OccurredAtUtc: DateTime.Parse(reader.GetString(1),
|
||||
System.Globalization.CultureInfo.InvariantCulture,
|
||||
System.Globalization.DateTimeStyles.RoundtripKind),
|
||||
Channel = Enum.Parse<AuditChannel>(reader.GetString(2)),
|
||||
Kind = Enum.Parse<AuditKind>(reader.GetString(3)),
|
||||
CorrelationId = reader.IsDBNull(4) ? null : Guid.Parse(reader.GetString(4)),
|
||||
SourceSiteId = reader.IsDBNull(5) ? null : reader.GetString(5),
|
||||
SourceNode = reader.IsDBNull(6) ? null : reader.GetString(6),
|
||||
SourceInstanceId = reader.IsDBNull(7) ? null : reader.GetString(7),
|
||||
SourceScript = reader.IsDBNull(8) ? null : reader.GetString(8),
|
||||
Actor = reader.IsDBNull(9) ? null : reader.GetString(9),
|
||||
Target = reader.IsDBNull(10) ? null : reader.GetString(10),
|
||||
Status = Enum.Parse<AuditStatus>(reader.GetString(11)),
|
||||
HttpStatus = reader.IsDBNull(12) ? null : reader.GetInt32(12),
|
||||
DurationMs = reader.IsDBNull(13) ? null : reader.GetInt32(13),
|
||||
ErrorMessage = reader.IsDBNull(14) ? null : reader.GetString(14),
|
||||
ErrorDetail = reader.IsDBNull(15) ? null : reader.GetString(15),
|
||||
RequestSummary = reader.IsDBNull(16) ? null : reader.GetString(16),
|
||||
ResponseSummary = reader.IsDBNull(17) ? null : reader.GetString(17),
|
||||
PayloadTruncated = reader.GetInt32(18) != 0,
|
||||
Extra = reader.IsDBNull(19) ? null : reader.GetString(19),
|
||||
ForwardState = Enum.Parse<AuditForwardState>(reader.GetString(20)),
|
||||
ExecutionId = reader.IsDBNull(21) ? null : Guid.Parse(reader.GetString(21)),
|
||||
ParentExecutionId = reader.IsDBNull(22) ? null : Guid.Parse(reader.GetString(22)),
|
||||
};
|
||||
IngestedAtUtc: null,
|
||||
Channel: Enum.Parse<AuditChannel>(reader.GetString(2)),
|
||||
Kind: Enum.Parse<AuditKind>(reader.GetString(3)),
|
||||
Status: Enum.Parse<AuditStatus>(reader.GetString(11)),
|
||||
CorrelationId: reader.IsDBNull(4) ? null : Guid.Parse(reader.GetString(4)),
|
||||
ExecutionId: reader.IsDBNull(21) ? null : Guid.Parse(reader.GetString(21)),
|
||||
ParentExecutionId: reader.IsDBNull(22) ? null : Guid.Parse(reader.GetString(22)),
|
||||
SourceSiteId: reader.IsDBNull(5) ? null : reader.GetString(5),
|
||||
SourceNode: reader.IsDBNull(6) ? null : reader.GetString(6),
|
||||
SourceInstanceId: reader.IsDBNull(7) ? null : reader.GetString(7),
|
||||
SourceScript: reader.IsDBNull(8) ? null : reader.GetString(8),
|
||||
Actor: reader.IsDBNull(9) ? null : reader.GetString(9),
|
||||
Target: reader.IsDBNull(10) ? null : reader.GetString(10),
|
||||
HttpStatus: reader.IsDBNull(12) ? null : reader.GetInt32(12),
|
||||
DurationMs: reader.IsDBNull(13) ? null : reader.GetInt32(13),
|
||||
ErrorMessage: reader.IsDBNull(14) ? null : reader.GetString(14),
|
||||
ErrorDetail: reader.IsDBNull(15) ? null : reader.GetString(15),
|
||||
RequestSummary: reader.IsDBNull(16) ? null : reader.GetString(16),
|
||||
ResponseSummary: reader.IsDBNull(17) ? null : reader.GetString(17),
|
||||
PayloadTruncated: reader.GetInt32(18) != 0,
|
||||
Extra: reader.IsDBNull(19) ? null : reader.GetString(19)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -898,15 +902,19 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
|
||||
private sealed class PendingAuditEvent
|
||||
{
|
||||
/// <summary>Initializes a new instance of the PendingAuditEvent class.</summary>
|
||||
/// <param name="evt">The audit event to persist.</param>
|
||||
public PendingAuditEvent(AuditEvent evt)
|
||||
/// <param name="evt">The canonical audit event to persist.</param>
|
||||
/// <param name="forwardState">Site-local forwarding state stored alongside the canonical row (C3 shim — not a canonical field).</param>
|
||||
public PendingAuditEvent(AuditEvent evt, AuditForwardState forwardState)
|
||||
{
|
||||
Event = evt;
|
||||
ForwardState = forwardState;
|
||||
Completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
}
|
||||
|
||||
/// <summary>The audit event to persist.</summary>
|
||||
/// <summary>The canonical audit event to persist.</summary>
|
||||
public AuditEvent Event { get; }
|
||||
/// <summary>Site-local forwarding state for this row (C3 shim — bound to the ForwardState column).</summary>
|
||||
public AuditForwardState ForwardState { get; }
|
||||
/// <summary>Task completion source for write completion signaling.</summary>
|
||||
public TaskCompletionSource Completion { get; }
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Integration;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site.Telemetry;
|
||||
@@ -141,37 +141,33 @@ public sealed class CachedCallLifecycleBridge : ICachedCallLifecycleObserver
|
||||
var channel = ChannelStringToEnum(context.Channel);
|
||||
|
||||
return new CachedCallTelemetry(
|
||||
Audit: new AuditEvent
|
||||
{
|
||||
EventId = Guid.NewGuid(),
|
||||
OccurredAtUtc = DateTime.SpecifyKind(context.OccurredAtUtc, DateTimeKind.Utc),
|
||||
Channel = channel,
|
||||
Kind = kind,
|
||||
CorrelationId = context.TrackedOperationId.Value,
|
||||
Audit: ScadaBridgeAuditEventFactory.Create(
|
||||
channel: channel,
|
||||
kind: kind,
|
||||
status: status,
|
||||
occurredAtUtc: DateTime.SpecifyKind(context.OccurredAtUtc, DateTimeKind.Utc),
|
||||
target: context.Target,
|
||||
correlationId: context.TrackedOperationId.Value,
|
||||
// Audit Log #23 (ExecutionId Task 4): the originating script
|
||||
// execution's per-run correlation id, threaded through the S&F
|
||||
// buffer; null on rows buffered before Task 4 (back-compat).
|
||||
ExecutionId = context.ExecutionId,
|
||||
executionId: context.ExecutionId,
|
||||
// Audit Log #23 (ParentExecutionId Task 6): the spawning
|
||||
// inbound-API request's ExecutionId, threaded through the S&F
|
||||
// buffer alongside ExecutionId so the retry-loop cached rows
|
||||
// correlate back to the cross-execution chain. Null for a
|
||||
// non-routed run and on rows buffered before Task 6.
|
||||
ParentExecutionId = context.ParentExecutionId,
|
||||
SourceSiteId = string.IsNullOrEmpty(context.SourceSite) ? null : context.SourceSite,
|
||||
SourceInstanceId = context.SourceInstanceId,
|
||||
parentExecutionId: context.ParentExecutionId,
|
||||
sourceSiteId: string.IsNullOrEmpty(context.SourceSite) ? null : context.SourceSite,
|
||||
sourceInstanceId: context.SourceInstanceId,
|
||||
// Audit Log #23 (ExecutionId Task 4): SourceScript is now
|
||||
// threaded through the S&F buffer alongside ExecutionId — the
|
||||
// retry-loop cached rows carry the same provenance the
|
||||
// script-side cached rows do. Null on pre-Task-4 buffered rows.
|
||||
SourceScript = context.SourceScript,
|
||||
Target = context.Target,
|
||||
Status = status,
|
||||
HttpStatus = httpStatus,
|
||||
DurationMs = context.DurationMs,
|
||||
ErrorMessage = lastError,
|
||||
ForwardState = AuditForwardState.Pending,
|
||||
},
|
||||
sourceScript: context.SourceScript,
|
||||
httpStatus: httpStatus,
|
||||
durationMs: context.DurationMs,
|
||||
errorMessage: lastError),
|
||||
Operational: new SiteCallOperational(
|
||||
TrackedOperationId: context.TrackedOperationId,
|
||||
Channel: context.Channel,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Integration;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site.Telemetry;
|
||||
@@ -111,9 +111,11 @@ public sealed class CachedCallTelemetryForwarder : ICachedCallTelemetryForwarder
|
||||
// FallbackAuditWriter) handles transient writer failures upstream;
|
||||
// a throw bubbling up here means the writer's own swallow contract
|
||||
// failed, which is itself best-effort-handled.
|
||||
// C3: Kind/Status are domain fields carried in DetailsJson — decompose to log them.
|
||||
var d = AuditRowProjection.Decompose(telemetry.Audit);
|
||||
_logger.LogWarning(ex,
|
||||
"CachedCallTelemetryForwarder: audit emission threw for EventId {EventId} (Kind {Kind}, Status {Status})",
|
||||
telemetry.Audit.EventId, telemetry.Audit.Kind, telemetry.Audit.Status);
|
||||
d.EventId, d.Kind, d.Status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,9 +130,12 @@ public sealed class CachedCallTelemetryForwarder : ICachedCallTelemetryForwarder
|
||||
return;
|
||||
}
|
||||
|
||||
// C3: the audit half's domain fields (Kind/SourceInstanceId/SourceScript)
|
||||
// ride inside DetailsJson — decompose once for this packet.
|
||||
var audit = AuditRowProjection.Decompose(telemetry.Audit);
|
||||
try
|
||||
{
|
||||
switch (telemetry.Audit.Kind)
|
||||
switch (audit.Kind)
|
||||
{
|
||||
case AuditKind.CachedSubmit:
|
||||
// Enqueue — insert-if-not-exists with the operational
|
||||
@@ -144,8 +149,8 @@ public sealed class CachedCallTelemetryForwarder : ICachedCallTelemetryForwarder
|
||||
telemetry.Operational.TrackedOperationId,
|
||||
telemetry.Operational.Channel,
|
||||
telemetry.Operational.Target,
|
||||
telemetry.Audit.SourceInstanceId,
|
||||
telemetry.Audit.SourceScript,
|
||||
audit.SourceInstanceId,
|
||||
audit.SourceScript,
|
||||
sourceNode: _nodeIdentity?.NodeName,
|
||||
ct).ConfigureAwait(false);
|
||||
break;
|
||||
@@ -180,7 +185,7 @@ public sealed class CachedCallTelemetryForwarder : ICachedCallTelemetryForwarder
|
||||
// forwarder.
|
||||
_logger.LogWarning(
|
||||
"CachedCallTelemetryForwarder: unexpected audit kind {Kind} on tracking emission for EventId {EventId}",
|
||||
telemetry.Audit.Kind, telemetry.Audit.EventId);
|
||||
audit.Kind, audit.EventId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Akka.Actor;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
|
||||
using ZB.MOM.WW.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
|
||||
|
||||
|
||||
@@ -2,10 +2,11 @@ using Akka.Actor;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
|
||||
using ZB.MOM.WW.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site.Telemetry;
|
||||
@@ -259,8 +260,8 @@ public class SiteAuditTelemetryActor : ReceiveActor
|
||||
// row stays Pending (still not in emittedEventIds) and
|
||||
// central reconciliation will pick it up.
|
||||
_logger.LogWarning(
|
||||
"Cached-telemetry drain: audit row {EventId} ({Kind}) has no CorrelationId; skipping.",
|
||||
auditRow.EventId, auditRow.Kind);
|
||||
"Cached-telemetry drain: audit row {EventId} ({Action}) has no CorrelationId; skipping.",
|
||||
auditRow.EventId, auditRow.Action);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -363,10 +364,13 @@ public class SiteAuditTelemetryActor : ReceiveActor
|
||||
private static CachedTelemetryPacket BuildCachedPacket(
|
||||
AuditEvent auditRow, TrackingStatusSnapshot snapshot)
|
||||
{
|
||||
var sourceSite = auditRow.SourceSiteId ?? string.Empty;
|
||||
// C3: SourceSiteId + Channel ride inside the canonical record's
|
||||
// DetailsJson — decompose to read them.
|
||||
var audit = AuditRowProjection.Decompose(auditRow);
|
||||
var sourceSite = audit.SourceSiteId ?? string.Empty;
|
||||
// Channel string form mirrors the AuditChannel-to-string convention used
|
||||
// by SiteCallOperational + CachedCallLifecycleBridge.BuildPacket.
|
||||
var channelString = auditRow.Channel.ToString();
|
||||
var channelString = audit.Channel.ToString();
|
||||
var target = auditRow.Target ?? snapshot.TargetSummary ?? string.Empty;
|
||||
|
||||
var operationalDto = new SiteCallOperationalDto
|
||||
|
||||
Reference in New Issue
Block a user