43 lines
1.8 KiB
C#
43 lines
1.8 KiB
C#
using ZB.MOM.WW.Audit;
|
|
|
|
namespace ZB.MOM.WW.MxGateway.Server.Security.Audit;
|
|
|
|
/// <summary>
|
|
/// Best-effort <see cref="IAuditWriter"/> over the MxGateway-owned
|
|
/// <see cref="SqliteCanonicalAuditStore"/>. It honours the canonical
|
|
/// <see cref="IAuditWriter"/> contract: a failed audit write is swallowed and logged
|
|
/// rather than propagated, so it can never abort the user-facing action that produced it.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This is the single sink through which ALL MxGateway audit flows — the library admin
|
|
/// verbs (via <see cref="CanonicalForwardingApiKeyAuditStore"/>) and the gateway's own
|
|
/// dashboard / constraint-denial producers, which write canonical events directly. The
|
|
/// best-effort wrapping here also closes the gap that the library's
|
|
/// <c>SqliteApiKeyAuditStore.AppendAsync</c> propagated exceptions.
|
|
/// </remarks>
|
|
public sealed class CanonicalAuditWriter(
|
|
SqliteCanonicalAuditStore store,
|
|
ILogger<CanonicalAuditWriter> logger) : IAuditWriter
|
|
{
|
|
/// <inheritdoc />
|
|
public async Task WriteAsync(AuditEvent auditEvent, CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(auditEvent);
|
|
|
|
try
|
|
{
|
|
await store.InsertAsync(auditEvent, cancellationToken).ConfigureAwait(false);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
// Best-effort: a failed audit write must never abort the action that produced it.
|
|
// Swallow everything (including OperationCanceledException) and log for diagnosis.
|
|
logger.LogWarning(
|
|
exception,
|
|
"Failed to persist audit event {EventId} (action {Action}); audit write is best-effort and was suppressed.",
|
|
auditEvent.EventId,
|
|
auditEvent.Action);
|
|
}
|
|
}
|
|
}
|