fix(audit): trace/outbound fault continuations run on the default scheduler and central emitters log (arch-review F8)

Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
This commit is contained in:
Joseph Doherty
2026-09-06 14:28:44 -04:00
parent b8752cc855
commit 262de38cee
6 changed files with 93 additions and 18 deletions
@@ -2,6 +2,7 @@ using System.Data.Common;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using Microsoft.Extensions.Logging;
using ZB.MOM.WW.Audit;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
@@ -70,6 +71,7 @@ public sealed class InboundDatabaseHelper
private readonly Guid? _parentExecutionId;
private readonly string? _actor;
private readonly Action<AuditEvent>? _traceSink;
private readonly ILogger? _logger;
/// <summary>
/// Initializes the helper.
@@ -83,6 +85,7 @@ public sealed class InboundDatabaseHelper
/// <param name="parentExecutionId">The spawning execution's id, if any (an inbound request is a root, so callers normally pass null).</param>
/// <param name="methodName">The executing API method's name; the row's <c>Actor</c> becomes <c>api:{methodName}</c>. Null = NULL actor.</param>
/// <param name="traceSink">Flow Debugger trace sink; receives the same event instance before the writer does. Null = none.</param>
/// <param name="logger">Arch-review F8: logs a Warning when the best-effort audit write faults; null = silent.</param>
// Trailing optional parameters: the original (gateway, ct, commandTimeout) shape stays
// source-compatible for every existing caller and test.
public InboundDatabaseHelper(
@@ -94,7 +97,8 @@ public sealed class InboundDatabaseHelper
Guid? executionId = null,
Guid? parentExecutionId = null,
string? methodName = null,
Action<AuditEvent>? traceSink = null)
Action<AuditEvent>? traceSink = null,
ILogger? logger = null)
{
_gateway = gateway;
_ct = ct;
@@ -107,6 +111,7 @@ public sealed class InboundDatabaseHelper
_parentExecutionId = parentExecutionId;
_actor = string.IsNullOrEmpty(methodName) ? null : "api:" + methodName;
_traceSink = traceSink;
_logger = logger;
}
/// <summary>First column of the first row converted to <typeparamref name="T"/> (default if no rows).</summary>
@@ -268,7 +273,11 @@ public sealed class InboundDatabaseHelper
var t = _auditWriter.WriteAsync(evt, CancellationToken.None);
if (!t.IsCompletedSuccessfully)
{
_ = t.ContinueWith(static _ => { }, TaskContinuationOptions.OnlyOnFaulted);
_ = t.ContinueWith(
x => _logger?.LogWarning(x.Exception, "Flow trace DbOutbound write failed for {Connection}", connectionName),
CancellationToken.None,
TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
}
}
catch (Exception)