diff --git a/src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs b/src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs index 77848e2..410448a 100644 --- a/src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs +++ b/src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs @@ -16,14 +16,19 @@ public sealed class ErrorLogCollector(int loginFailureBurst = 20) : CollectorOn the first collection, this far back. public static readonly TimeSpan FirstLookBack = TimeSpan.FromHours(1); + /// How much the ring buffer's computed times can move between two reads. + internal static readonly TimeSpan RingBufferClockTolerance = TimeSpan.FromMilliseconds(50); + internal const string CanReadErrorLogSql = """ SELECT CASE WHEN IS_SRVROLEMEMBER(N'securityadmin') = 1 OR IS_SRVROLEMEMBER(N'sysadmin') = 1 THEN 1 ELSE 0 END, DATEDIFF(minute, GETUTCDATE(), GETDATE()), GETUTCDATE() """; // The exception ring buffer: every exception raised, with number and severity. ms_ticks is the clock it counts in. + // The time is computed to the millisecond: rounded to whole seconds it moved by up to a second between collections, + // so the newest entry could be read (and counted) again. internal const string RingBufferSql = """ - SELECT DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE()), + SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())), rb.x.value('(//Exception/Error)[1]', 'int'), rb.x.value('(//Exception/Severity)[1]', 'int') FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers WHERE ring_buffer_type = N'RING_BUFFER_EXCEPTION') rb CROSS JOIN sys.dm_os_sys_info i @@ -31,7 +36,7 @@ public sealed class ErrorLogCollector(int loginFailureBurst = 20) : Collector18456%') rb @@ -74,12 +79,15 @@ public sealed class ErrorLogCollector(int loginFailureBurst = 20) : Collector new ErrorLogEntry(Utc(r.DateTime(0)), r.NullableInt(1), r.NullableInt(2), null), ct) - .ConfigureAwait(false)).Where(e => e.At > since).ToList(); + .ConfigureAwait(false)).Where(e => e.At > after).ToList(); if (context.Instance.Version.Major >= 10) { entries.AddRange((await Db.QueryAsync(connection, ConnectivitySql, r => new ErrorLogEntry(Utc(r.DateTime(0)), 18456, 14, - $"Login failed (state {r.NullableInt(2)}) [CLIENT: {r.NullableString(3)}]"), ct).ConfigureAwait(false)).Where(e => e.At > since)); + $"Login failed (state {r.NullableInt(2)}) [CLIENT: {r.NullableString(3)}]"), ct).ConfigureAwait(false)).Where(e => e.At > after)); } (source, coverage) = (ErrorLogSource.RingBuffer, RingBufferCoverage); @@ -154,7 +162,9 @@ public sealed class ErrorLogCollector(int loginFailureBurst = 20) : Collector e.At > since)]; } catch (DbException) {