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>
56 lines
2.8 KiB
C#
56 lines
2.8 KiB
C#
using SQLInsight.Core.Data;
|
|
using SQLInsight.Core.Findings;
|
|
using SQLInsight.Rules.Engine;
|
|
using SQLInsight.Rules.Instance;
|
|
using static SQLInsight.Rules.Tests.TestInstances;
|
|
|
|
namespace SQLInsight.Rules.Tests;
|
|
|
|
public class ErrorLogRuleTests
|
|
{
|
|
private static RuleResult Run(IRule rule, RuleContext context) => Assert.Single(new RuleEngine([rule]).Evaluate(context));
|
|
|
|
private static ErrorLogSnapshot Log(ErrorLogSource source, params ErrorLogGroup[] groups) => new()
|
|
{
|
|
CapturedAt = Now, Source = source, ReadUntil = Now, Groups = groups, Recent = groups, LoginFailureBurst = 20,
|
|
Coverage = source == ErrorLogSource.ErrorLog ? null : "Giv agenten ALTER TRACE eller securityadmin",
|
|
};
|
|
|
|
private static ErrorLogGroup G(ErrorLogCategory category, int error, int severity, int count = 1) =>
|
|
new(category, error, severity, count, Now.AddMinutes(-3), Now.AddMinutes(-1), "Error text with login 'anne'");
|
|
|
|
[Theory]
|
|
[InlineData(824, Severity.Critical)]
|
|
[InlineData(825, Severity.High)]
|
|
public void Corruption_is_critical_and_read_retries_high(int error, Severity expected)
|
|
{
|
|
var finding = Assert.Single(Run(new CorruptionErrorRule(), With(Log(ErrorLogSource.DefaultTrace, G(ErrorLogCategory.Corruption, error, 24)))).Findings);
|
|
|
|
Assert.Equal(expected, finding.Severity);
|
|
Assert.True(finding.Evidence.Single(e => e.Name == "Eksempel").Sensitive);
|
|
}
|
|
|
|
[Fact]
|
|
public void Slow_io_and_stack_dumps_need_the_text_of_the_log()
|
|
{
|
|
var result = Run(new SlowIoErrorRule(), With(Log(ErrorLogSource.RingBuffer)));
|
|
|
|
Assert.Equal(RuleOutcome.NotMeasurable, result.Outcome);
|
|
Assert.Contains("ALTER TRACE", result.Reason, StringComparison.Ordinal);
|
|
Assert.Equal(RuleOutcome.NotMeasurable, Run(new SlowIoErrorRule(), With(Log(ErrorLogSource.DefaultTrace))).Outcome);
|
|
Assert.Equal(RuleOutcome.Findings, Run(new SlowIoErrorRule(), With(Log(ErrorLogSource.ErrorLog, G(ErrorLogCategory.SlowIo, 833, 10)))).Outcome);
|
|
Assert.Equal(RuleOutcome.NotMeasurable, Run(new StackDumpRule(), With(Log(ErrorLogSource.RingBuffer))).Outcome);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(19, RuleOutcome.Passed)]
|
|
[InlineData(20, RuleOutcome.Findings)]
|
|
public void Login_failures_are_a_finding_from_the_burst_size(int count, RuleOutcome expected) =>
|
|
Assert.Equal(expected, Run(new LoginFailureBurstRule(), With(Log(ErrorLogSource.RingBuffer, G(ErrorLogCategory.LoginFailed, 18456, 14, count)))).Outcome);
|
|
|
|
[Theory]
|
|
[InlineData(17, Severity.Medium)]
|
|
[InlineData(21, Severity.High)]
|
|
public void Severe_errors_by_severity(int severity, Severity expected) =>
|
|
Assert.Equal(expected, Assert.Single(Run(new SevereErrorRule(), With(Log(ErrorLogSource.RingBuffer, G(ErrorLogCategory.Severe, 701, severity)))).Findings).Severity);
|
|
}
|