fix(auditlog): truncate over-width SiteCalls.LastError on the cached-telemetry dual-write and the reconciliation pull (review 2026-10-21 follow-up)
This commit is contained in:
@@ -435,7 +435,7 @@ public class AuditLogIngestActor : ReceiveActor
|
||||
var safeRedactor = redactor ?? SafeDefaultAuditRedactor.Instance;
|
||||
var filteredAudit = safeRedactor.Apply(entry.Audit);
|
||||
var auditStamped = AuditRowProjection.WithIngestedAtUtc(filteredAudit, ingestedAt);
|
||||
var siteCallStamped = entry.SiteCall with { IngestedAtUtc = ingestedAt };
|
||||
var siteCallStamped = SiteCallColumnBounds.Clamp(entry.SiteCall) with { IngestedAtUtc = ingestedAt };
|
||||
|
||||
await auditRepo.InsertIfNotExistsAsync(auditStamped, fallbackBudget.Token)
|
||||
.ConfigureAwait(false);
|
||||
@@ -539,7 +539,7 @@ public class AuditLogIngestActor : ReceiveActor
|
||||
foreach (var entry in cmd.Entries)
|
||||
{
|
||||
await siteCallRepo
|
||||
.UpsertAsync(entry.SiteCall with { IngestedAtUtc = ingestedAt }, ct)
|
||||
.UpsertAsync(SiteCallColumnBounds.Clamp(entry.SiteCall) with { IngestedAtUtc = ingestedAt }, ct)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -141,7 +141,9 @@ public sealed class GrpcPullSiteCallsClient : IPullSiteCallsClient
|
||||
{
|
||||
try
|
||||
{
|
||||
var sc = SiteCallDtoMapper.FromDto(dto) with { SourceSite = siteId };
|
||||
// Fit LastError to the central column so the reconciliation upsert cannot fail
|
||||
// with SQL 8152 (review 2026-10-21 follow-up — see SiteCallColumnBounds).
|
||||
var sc = SiteCallColumnBounds.Clamp(SiteCallDtoMapper.FromDto(dto)) with { SourceSite = siteId };
|
||||
siteCalls.Add(sc);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Central;
|
||||
|
||||
/// <summary>
|
||||
/// Fits a site-supplied <see cref="SiteCall"/> to the central <c>SiteCalls</c> column widths before
|
||||
/// it is written (review 2026-10-21 follow-up). The site's tracking store does not bound
|
||||
/// <see cref="SiteCall.LastError"/>, while the central column is <c>nvarchar(1024)</c>
|
||||
/// (<c>SiteCallEntityTypeConfiguration</c>): an over-width error used to fail the
|
||||
/// cached-telemetry dual-write with SQL 8152 — the audit row was reported Rejected to the site
|
||||
/// and the reconciliation pull's upsert failed the same way, so the <c>SiteCalls</c> row was never
|
||||
/// written at all. Truncating a diagnostic message is the right trade; dropping the row is not.
|
||||
/// </summary>
|
||||
internal static class SiteCallColumnBounds
|
||||
{
|
||||
/// <summary>Width of <c>SiteCalls.LastError</c>; must match <c>SiteCallEntityTypeConfiguration</c>.</summary>
|
||||
public const int LastErrorMaxLength = 1024;
|
||||
|
||||
/// <summary>
|
||||
/// Returns <paramref name="siteCall"/> unchanged when it already fits, otherwise a copy whose
|
||||
/// <see cref="SiteCall.LastError"/> is truncated to <see cref="LastErrorMaxLength"/> characters
|
||||
/// (never splitting a UTF-16 surrogate pair).
|
||||
/// </summary>
|
||||
/// <param name="siteCall">The row about to be written centrally.</param>
|
||||
/// <returns>A row that fits the central columns.</returns>
|
||||
public static SiteCall Clamp(SiteCall siteCall)
|
||||
{
|
||||
if (siteCall.LastError is not { Length: > LastErrorMaxLength } error)
|
||||
{
|
||||
return siteCall;
|
||||
}
|
||||
|
||||
var cut = LastErrorMaxLength;
|
||||
if (char.IsHighSurrogate(error[cut - 1]))
|
||||
{
|
||||
cut--;
|
||||
}
|
||||
|
||||
return siteCall with { LastError = error[..cut] };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user