A five-minute collector reads important error log entries from the best
source the agent's rights allow: the error log itself (xp_readerrorlog,
only with securityadmin), the default trace's ErrorLog and Audit Login
Failed events (optional ALTER TRACE), or the exception and connectivity
ring buffers (VIEW SERVER STATE: numbers and severity, failed logins).
Repetitions are summed per error; the watermark is the newest entry
read, because the default trace is buffered. Stored in
insight.ErrorLogEvents (migration 0013, EventDays), resumed after restart.
Rules SQLI-LOG-001..006: corruption 823/824/825, I/O over 15 s (833),
severe errors (17+), login failure bursts (configurable), stack dumps and
AG errors; rules needing the log text are "not measurable" with the
right to grant. Page /instanser/{navn}/fejllog (menu Overvågning) and
GET /api/v1/instances/{name}/error-log with messages redacted.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
54 lines
2.8 KiB
C#
54 lines
2.8 KiB
C#
using SQLInsight.Core.Data;
|
|
|
|
namespace SQLInsight.Core.Tests;
|
|
|
|
public class ErrorLogTests
|
|
{
|
|
private static readonly DateTimeOffset T = new(2026, 10, 2, 8, 0, 0, TimeSpan.Zero);
|
|
|
|
[Fact]
|
|
public void Number_lines_are_joined_to_the_message_that_follows()
|
|
{
|
|
var entries = ErrorLogAnalysis.ParseLines(
|
|
[
|
|
(T, "Error: 824, Severity: 24, State: 2."),
|
|
(T, "SQL Server detected a logical consistency-based I/O error: incorrect checksum."),
|
|
(T.AddSeconds(1), "SQL Server has encountered 3 occurrence(s) of I/O requests taking longer than 15 seconds to complete on file [D:\\Data\\AX.mdf]"),
|
|
(T.AddSeconds(2), "Login failed for user 'sa'. Reason: Password did not match. [CLIENT: 10.0.0.9]"),
|
|
(T.AddSeconds(3), "Error: 17310, Severity: 20, State: 1."),
|
|
]);
|
|
|
|
Assert.Equal(4, entries.Count);
|
|
Assert.Equal((824, 24), (entries[0].Error, entries[0].Severity));
|
|
Assert.Contains("checksum", entries[0].Message, StringComparison.Ordinal);
|
|
Assert.Equal(833, entries[1].Error);
|
|
Assert.Equal(18456, entries[2].Error);
|
|
Assert.Equal(17310, entries[3].Error);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(825, 10, null, ErrorLogCategory.Corruption)]
|
|
[InlineData(833, 10, null, ErrorLogCategory.SlowIo)]
|
|
[InlineData(18456, 14, null, ErrorLogCategory.LoginFailed)]
|
|
[InlineData(35206, 16, null, ErrorLogCategory.AvailabilityGroup)]
|
|
[InlineData(null, null, "***Stack Dump being sent to C:\\…\\SQLDump0001.txt", ErrorLogCategory.StackDump)]
|
|
[InlineData(701, 17, null, ErrorLogCategory.Severe)]
|
|
[InlineData(5701, 10, "Changed database context to 'master'.", ErrorLogCategory.Other)]
|
|
public void Entries_are_categorized(int? error, int? severity, string? message, ErrorLogCategory expected) =>
|
|
Assert.Equal(expected, ErrorLogAnalysis.Categorize(new ErrorLogEntry(T, error, severity, message)));
|
|
|
|
[Fact]
|
|
public void Repetitions_are_summed_and_kept_for_a_day()
|
|
{
|
|
var first = ErrorLogAnalysis.Group(Enumerable.Range(0, 30).Select(i => new ErrorLogEntry(T.AddSeconds(i), 18456, 14, "Login failed for user 'x'.")));
|
|
var later = ErrorLogAnalysis.Group([new ErrorLogEntry(T.AddHours(2), 18456, 14, "Login failed for user 'y'.")]);
|
|
|
|
var login = Assert.Single(first);
|
|
Assert.Equal((30, T, T.AddSeconds(29)), (login.Count, login.FirstAt, login.LastAt));
|
|
var recent = Assert.Single(ErrorLogAnalysis.Recent(first, later, T.AddHours(2)));
|
|
Assert.Equal(31, recent.Count);
|
|
Assert.Contains("'y'", recent.Sample, StringComparison.Ordinal);
|
|
Assert.Single(ErrorLogAnalysis.Recent(first, later, T.AddHours(25))); // the first batch has expired
|
|
Assert.Equal(1, ErrorLogAnalysis.Recent(first, later, T.AddHours(25))[0].Count);
|
|
}
|
|
}
|