Skip to content

Commit cf95420

Browse files
committed
Add AutoShowLogDisplayThreshold option to logger
1 parent 805dae5 commit cf95420

File tree

6 files changed

+157
-21
lines changed

6 files changed

+157
-21
lines changed

Source/ExcelRna.Extensions.Logging.Tests/LogDisplayLoggerProviderTests.cs

+74-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
using System;
2+
using System.Collections.Concurrent;
3+
using Microsoft.Extensions.Logging;
4+
using Microsoft.Extensions.Options;
5+
using Moq;
16
using Xunit;
27

38
namespace ExcelRna.Extensions.Logging.Tests;
@@ -8,7 +13,9 @@ public class LogDisplayLoggerProviderTests
813
public void CreateLogger_creates_LogDisplayLogger()
914
{
1015
// ARRANGE
11-
var provider = new LogDisplayLoggerProvider();
16+
var options = new LogDisplayLoggerOptions();
17+
var optionsMonitor = Mock.Of<IOptionsMonitor<LogDisplayLoggerOptions>>(monitor => monitor.CurrentValue == options);
18+
var provider = new LogDisplayLoggerProvider(optionsMonitor);
1219

1320
// ACT
1421
var logger = provider.CreateLogger("Test");
@@ -21,9 +28,74 @@ public void CreateLogger_creates_LogDisplayLogger()
2128
public void Dispose_does_not_throw()
2229
{
2330
// ARRANGE
24-
var provider = new LogDisplayLoggerProvider();
31+
var options = new LogDisplayLoggerOptions();
32+
var optionsMonitor = Mock.Of<IOptionsMonitor<LogDisplayLoggerOptions>>(monitor => monitor.CurrentValue == options);
33+
Mock.Get(optionsMonitor)
34+
.Setup(monitor => monitor.OnChange(It.IsAny<Action<LogDisplayLoggerOptions, string>>()))
35+
.Returns(Mock.Of<IDisposable>());
36+
var provider = new LogDisplayLoggerProvider(optionsMonitor);
2537

2638
// ACT
2739
provider.Dispose();
2840
}
41+
42+
[Fact]
43+
public void Options_changes_are_applied_to_loggers()
44+
{
45+
// ARRANGE
46+
var optionsMonitor = new TestOptionsMonitor<LogDisplayLoggerOptions>();
47+
optionsMonitor.Set(Options.DefaultName, new LogDisplayLoggerOptions { AutoShowLogDisplayThreshold = LogLevel.Error });
48+
49+
var provider = new LogDisplayLoggerProvider(optionsMonitor);
50+
var logger = (LogDisplayLogger)provider.CreateLogger("Test");
51+
52+
// ACT & ASSERT
53+
Assert.Equal(LogLevel.Error, logger.Options.AutoShowLogDisplayThreshold);
54+
optionsMonitor.Set(Options.DefaultName, new LogDisplayLoggerOptions { AutoShowLogDisplayThreshold = LogLevel.Trace });
55+
Assert.Equal(LogLevel.Trace, logger.Options.AutoShowLogDisplayThreshold);
56+
}
57+
58+
private class TestOptionsMonitor<TOptions> : IOptionsMonitor<TOptions>
59+
where TOptions : class, new()
60+
{
61+
private readonly ConcurrentDictionary<string, TOptions> _options = new();
62+
private event Action<TOptions, string> OnChangeEvent;
63+
64+
public TOptions Get(string name)
65+
{
66+
return _options.TryGetValue(name ?? Options.DefaultName, out var options) ? options : throw new Exception(nameof(name));
67+
}
68+
69+
public void Set(string name, TOptions options)
70+
{
71+
_options.TryRemove(name, out _);
72+
_options.TryAdd(name, options);
73+
OnChangeEvent?.Invoke(options, name);
74+
}
75+
76+
public IDisposable OnChange(Action<TOptions, string> listener)
77+
{
78+
var disposable = new ChangeTrackerDisposable(this, listener);
79+
OnChangeEvent += disposable.OnChange;
80+
return disposable;
81+
}
82+
83+
public TOptions CurrentValue => Get(Options.DefaultName);
84+
85+
private class ChangeTrackerDisposable : IDisposable
86+
{
87+
private readonly Action<TOptions, string> _listener;
88+
private readonly TestOptionsMonitor<TOptions> _monitor;
89+
90+
public ChangeTrackerDisposable(TestOptionsMonitor<TOptions> monitor, Action<TOptions, string> listener)
91+
{
92+
_listener = listener;
93+
_monitor = monitor;
94+
}
95+
96+
public void OnChange(TOptions options, string name) => _listener.Invoke(options, name);
97+
98+
public void Dispose() => _monitor.OnChangeEvent -= OnChange;
99+
}
100+
}
29101
}

Source/ExcelRna.Extensions.Logging.Tests/LogDisplayLoggerTests.cs

+37-14
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,10 @@ public void IsEnabled_returns_correct_value()
4040
public void Log_is_noop_when_not_enabled()
4141
{
4242
// ARRANGE
43-
var logger = new LogDisplayLogger("Test");
44-
var recordLine = new Mock<Action<string, string[]>>(MockBehavior.Strict);
45-
logger.RecordLine = recordLine.Object;
43+
var logger = new LogDisplayLogger("Test")
44+
{
45+
RecordLine = Mock.Of<Action<string, string[]>>(MockBehavior.Strict),
46+
};
4647

4748
// ACT
4849
logger.Log(LogLevel.None, "TestMessage");
@@ -52,9 +53,10 @@ public void Log_is_noop_when_not_enabled()
5253
public void Log_is_noop_when_message_is_empty()
5354
{
5455
// ARRANGE
55-
var logger = new LogDisplayLogger("Test");
56-
var recordLine = new Mock<Action<string, string[]>>(MockBehavior.Strict);
57-
logger.RecordLine = recordLine.Object;
56+
var logger = new LogDisplayLogger("Test")
57+
{
58+
RecordLine = Mock.Of<Action<string, string[]>>(MockBehavior.Strict),
59+
};
5860

5961
// ACT
6062
logger.Log(LogLevel.Information, "");
@@ -64,9 +66,10 @@ public void Log_is_noop_when_message_is_empty()
6466
public void Log_throws_when_formatter_is_null()
6567
{
6668
// ARRANGE
67-
var logger = new LogDisplayLogger("Test");
68-
var recordLine = new Mock<Action<string, string[]>>(MockBehavior.Strict);
69-
logger.RecordLine = recordLine.Object;
69+
var logger = new LogDisplayLogger("Test")
70+
{
71+
RecordLine = Mock.Of<Action<string, string[]>>(MockBehavior.Strict),
72+
};
7073

7174
// ACT & ASSERT
7275
Assert.Throws<ArgumentNullException>(() => logger.Log(LogLevel.Information, 0, "State", null, null!));
@@ -76,14 +79,34 @@ public void Log_throws_when_formatter_is_null()
7679
public void Log_includes_exception()
7780
{
7881
// ARRANGE
79-
var logger = new LogDisplayLogger("Test");
80-
var recordLine = new Mock<Action<string, string[]>>(MockBehavior.Strict);
81-
recordLine.Setup(invoke => invoke(
82+
var logger = new LogDisplayLogger("Test")
83+
{
84+
RecordLine = Mock.Of<Action<string, string[]>>(),
85+
};
86+
87+
// ACT
88+
logger.Log(LogLevel.Information, new Exception("TestException"), "TestMessage");
89+
90+
// ASSERT
91+
Mock.Get(logger.RecordLine).Verify(invoke => invoke(
8292
It.Is<string>(s => s.Contains("TestMessage") && s.Contains("TestException")),
83-
It.Is<string[]>(p => p.Length == 0)));
84-
logger.RecordLine = recordLine.Object;
93+
It.Is<string[]>(p => p.Length == 0)), Times.Once);
94+
}
95+
96+
[Fact]
97+
public void Log_auto_shows_LogDisplay()
98+
{
99+
// ARRANGE
100+
var logger = new LogDisplayLogger("Test")
101+
{
102+
RecordLine = Mock.Of<Action<string, string[]>>(),
103+
Show = Mock.Of<Action>(),
104+
};
85105

86106
// ACT
87107
logger.Log(LogLevel.Error, new Exception("TestException"), "TestMessage");
108+
109+
// ASSERT
110+
Mock.Get(logger.Show).Verify(invoke => invoke(), Times.Once);
88111
}
89112
}

Source/ExcelRna.Extensions.Logging/LogDisplayLogger.cs

+12-1
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,19 @@ internal sealed class LogDisplayLogger : ILogger
1515
/// Initializes a new instance of the <see cref="LogDisplayLogger"/> class.
1616
/// </summary>
1717
/// <param name="name">The name of the logger.</param>
18-
public LogDisplayLogger(string name)
18+
/// <param name="options">The options of the logger.</param>
19+
public LogDisplayLogger(string name, LogDisplayLoggerOptions? options = null)
1920
{
2021
_name = name;
22+
Options = options ?? new LogDisplayLoggerOptions();
2123
}
2224

2325
internal Action<string, string[]> RecordLine { get; set; } = LogDisplay.RecordLine;
2426

27+
internal Action Show { get; set; } = LogDisplay.Show;
28+
29+
internal LogDisplayLoggerOptions Options { get; set; }
30+
2531
/// <inheritdoc />
2632
public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None;
2733

@@ -56,5 +62,10 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except
5662
}
5763

5864
RecordLine(message, Array.Empty<string>());
65+
66+
if (logLevel >= Options.AutoShowLogDisplayThreshold)
67+
{
68+
Show();
69+
}
5970
}
6071
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using Microsoft.Extensions.Logging;
2+
3+
namespace ExcelRna.Extensions.Logging;
4+
5+
public class LogDisplayLoggerOptions
6+
{
7+
public LogLevel AutoShowLogDisplayThreshold { get; set; } = LogLevel.Error;
8+
}

Source/ExcelRna.Extensions.Logging/LogDisplayLoggerProvider.cs

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
using System;
2+
using System.Collections.Concurrent;
3+
using System.Collections.Generic;
14
using Microsoft.Extensions.Logging;
5+
using Microsoft.Extensions.Options;
26

37
namespace ExcelRna.Extensions.Logging;
48

@@ -8,14 +12,33 @@ namespace ExcelRna.Extensions.Logging;
812
[ProviderAlias("LogDisplay")]
913
public class LogDisplayLoggerProvider : ILoggerProvider
1014
{
15+
private readonly IOptionsMonitor<LogDisplayLoggerOptions> _options;
16+
private readonly IDisposable _optionsReloadToken;
17+
private readonly ConcurrentDictionary<string, LogDisplayLogger> _loggers = new();
18+
19+
public LogDisplayLoggerProvider(IOptionsMonitor<LogDisplayLoggerOptions> options)
20+
{
21+
_options = options;
22+
_optionsReloadToken = options.OnChange(ReloadOptions);
23+
}
24+
1125
/// <inheritdoc />
1226
public ILogger CreateLogger(string name)
1327
{
14-
return new LogDisplayLogger(name);
28+
return _loggers.GetOrAdd(name, n => new LogDisplayLogger(n, _options.CurrentValue));
1529
}
1630

1731
/// <inheritdoc />
1832
public void Dispose()
1933
{
34+
_optionsReloadToken.Dispose();
35+
}
36+
37+
private void ReloadOptions(LogDisplayLoggerOptions options)
38+
{
39+
foreach (KeyValuePair<string, LogDisplayLogger> logger in _loggers)
40+
{
41+
logger.Value.Options = options;
42+
}
2043
}
2144
}

Source/Samples/QuickStart/QuickStartAddIn.cs

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System;
2-
using System.Runtime.InteropServices;
1+
using System.Runtime.InteropServices;
32
using ExcelDna.Integration;
43
using ExcelDna.Integration.CustomUI;
54
using ExcelDna.IntelliSense;
@@ -86,7 +85,7 @@ public override string GetCustomUI(string ribbonId)
8685
";
8786
}
8887

89-
public void OnButtonPressed(IRibbonControl control) => _logger.LogInformation(new StackOverflowException(), _quickStartService.SayHello(control.Tag));
88+
public void OnButtonPressed(IRibbonControl control) => _logger.LogInformation(_quickStartService.SayHello(control.Tag));
9089

9190
public void OnLogDisplay(IRibbonControl control) => LogDisplay.Show();
9291
}

0 commit comments

Comments
 (0)