Skip to content

Commit 79b9f3d

Browse files
committed
Log levels should support prefix matching
1 parent 85f7a66 commit 79b9f3d

File tree

2 files changed

+88
-12
lines changed

2 files changed

+88
-12
lines changed

src/Foundatio.Xunit/Logging/TestLogger.cs

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ namespace Foundatio.Xunit;
99

1010
public class TestLogger : ILoggerFactory
1111
{
12-
private readonly Dictionary<string, LogLevel> _logLevels = new();
1312
private readonly Queue<LogEntry> _logEntries = new();
1413
private int _logEntriesWritten;
1514

@@ -19,7 +18,7 @@ public TestLogger(Action<TestLoggerOptions> configure = null)
1918
configure?.Invoke(Options);
2019

2120
foreach (var logLevel in Options.LogLevels)
22-
_logLevels[logLevel.Key] = logLevel.Value;
21+
SetLogLevel(logLevel.Key, logLevel.Value);
2322
}
2423

2524
public TestLogger(ITestOutputHelper output, Action<TestLoggerOptions> configure = null)
@@ -35,21 +34,21 @@ public TestLogger(ITestOutputHelper output, Action<TestLoggerOptions> configure
3534
configure?.Invoke(Options);
3635

3736
foreach (var logLevel in Options.LogLevels)
38-
_logLevels[logLevel.Key] = logLevel.Value;
37+
SetLogLevel(logLevel.Key, logLevel.Value);
3938
}
4039

4140
public TestLogger(TestLoggerOptions options)
4241
{
4342
Options = options ?? new TestLoggerOptions();
4443

4544
foreach (var logLevel in Options.LogLevels)
46-
_logLevels[logLevel.Key] = logLevel.Value;
45+
SetLogLevel(logLevel.Key, logLevel.Value);
4746

4847
}
4948

5049
public TestLoggerOptions Options { get; }
5150

52-
public LogLevel DefaultMinimumLevel
51+
public LogLevel DefaultLogLevel
5352
{
5453
get => Options.DefaultLogLevel;
5554
set => Options.DefaultLogLevel = value;
@@ -74,10 +73,20 @@ public void Reset()
7473
lock (_logEntries)
7574
{
7675
_logEntries.Clear();
77-
_logLevels.Clear();
76+
77+
_lock.EnterWriteLock();
78+
try
79+
{
80+
_root.Children.Clear();
81+
_root.Level = null;
82+
}
83+
finally
84+
{
85+
_lock.ExitWriteLock();
86+
}
7887

7988
foreach (var logLevel in Options.LogLevels)
80-
_logLevels[logLevel.Key] = logLevel.Value;
89+
SetLogLevel(logLevel.Key, logLevel.Value);
8190

8291
Interlocked.Exchange(ref _logEntriesWritten, 0);
8392
}
@@ -116,15 +125,74 @@ public void AddProvider(ILoggerProvider loggerProvider) { }
116125

117126
public bool IsEnabled(string category, LogLevel logLevel)
118127
{
119-
if (_logLevels.TryGetValue(category, out var categoryLevel))
120-
return logLevel >= categoryLevel;
128+
ReadOnlySpan<char> span = category.AsSpan();
129+
Span<(int Start, int Length)> segments = stackalloc (int, int)[20];
130+
131+
int count = 0;
132+
int start = 0;
133+
134+
for (int i = 0; i <= span.Length; i++)
135+
{
136+
if (i != span.Length && span[i] != '.')
137+
continue;
138+
139+
segments[count++] = (start, i - start);
140+
start = i + 1;
141+
142+
if (count == segments.Length)
143+
break;
144+
}
145+
146+
_lock.EnterReadLock();
147+
try {
148+
var current = _root;
149+
LogLevel effectiveLevel = DefaultLogLevel;
150+
151+
for (int i = 0; i < count; i++) {
152+
var segment = span.Slice(segments[i].Start, segments[i].Length);
153+
bool found = false;
121154

122-
return logLevel >= Options.DefaultLogLevel;
155+
foreach (var kvp in current.Children)
156+
{
157+
if (!segment.Equals(kvp.Key.AsSpan(), StringComparison.Ordinal))
158+
continue;
159+
160+
current = kvp.Value;
161+
found = true;
162+
163+
if (current.Level.HasValue)
164+
effectiveLevel = current.Level.Value;
165+
166+
break;
167+
}
168+
169+
if (!found)
170+
break;
171+
}
172+
173+
return logLevel >= effectiveLevel;
174+
} finally {
175+
_lock.ExitReadLock();
176+
}
123177
}
124178

125179
public void SetLogLevel(string category, LogLevel minLogLevel)
126180
{
127-
_logLevels[category] = minLogLevel;
181+
string[] parts = category.Split('.');
182+
_lock.EnterWriteLock();
183+
try {
184+
var current = _root;
185+
foreach (string part in parts) {
186+
if (!current.Children.TryGetValue(part, out var child)) {
187+
child = new Node();
188+
current.Children[part] = child;
189+
}
190+
current = child;
191+
}
192+
current.Level = minLogLevel;
193+
} finally {
194+
_lock.ExitWriteLock();
195+
}
128196
}
129197

130198
public void SetLogLevel<T>(LogLevel minLogLevel)
@@ -133,4 +201,12 @@ public void SetLogLevel<T>(LogLevel minLogLevel)
133201
}
134202

135203
public void Dispose() { }
204+
205+
private class Node {
206+
public readonly Dictionary<string, Node> Children = new(StringComparer.OrdinalIgnoreCase);
207+
public LogLevel? Level;
208+
}
209+
210+
private readonly Node _root = new();
211+
private readonly ReaderWriterLockSlim _lock = new();
136212
}

src/Foundatio.Xunit/Logging/TestLoggerOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace Foundatio.Xunit;
99
public class TestLoggerOptions
1010
{
1111
public LogLevel DefaultLogLevel { get; set; } = LogLevel.Information;
12-
public Dictionary<string, LogLevel> LogLevels { get; } = new();
12+
public Dictionary<string, LogLevel> LogLevels { get; } = new(StringComparer.OrdinalIgnoreCase);
1313
public int MaxLogEntriesToStore { get; set; } = 100;
1414
public int MaxLogEntriesToWrite { get; set; } = 1000;
1515
public bool IncludeScopes { get; set; } = true;

0 commit comments

Comments
 (0)