Skip to content

Commit 8e15a09

Browse files
authored
Merge pull request #19 from altso/log-timestamp
Support timestamps in log messages
2 parents ef028d4 + 240c00d commit 8e15a09

File tree

4 files changed

+121
-16
lines changed

4 files changed

+121
-16
lines changed

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

+72-10
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public void Log_is_noop_when_not_enabled()
4242
// ARRANGE
4343
var logger = new LogDisplayLogger("Test")
4444
{
45-
RecordLine = Mock.Of<Action<string, string[]>>(MockBehavior.Strict),
45+
RecordLine = Mock.Of<Action<string, object[]>>(MockBehavior.Strict),
4646
};
4747

4848
// ACT
@@ -55,7 +55,7 @@ public void Log_is_noop_when_message_is_empty()
5555
// ARRANGE
5656
var logger = new LogDisplayLogger("Test")
5757
{
58-
RecordLine = Mock.Of<Action<string, string[]>>(MockBehavior.Strict),
58+
RecordLine = Mock.Of<Action<string, object[]>>(MockBehavior.Strict),
5959
};
6060

6161
// ACT
@@ -68,7 +68,7 @@ public void Log_throws_when_formatter_is_null()
6868
// ARRANGE
6969
var logger = new LogDisplayLogger("Test")
7070
{
71-
RecordLine = Mock.Of<Action<string, string[]>>(MockBehavior.Strict),
71+
RecordLine = Mock.Of<Action<string, object[]>>(MockBehavior.Strict),
7272
};
7373

7474
// ACT & ASSERT
@@ -79,34 +79,96 @@ public void Log_throws_when_formatter_is_null()
7979
public void Log_includes_exception()
8080
{
8181
// ARRANGE
82-
var logger = new LogDisplayLogger("Test")
82+
var logger = new LogDisplayLogger("Test", new LogDisplayLoggerOptions
83+
{
84+
TimestampFormat = "G",
85+
})
8386
{
84-
RecordLine = Mock.Of<Action<string, string[]>>(),
87+
RecordLine = Mock.Of<Action<string, object[]>>(),
8588
};
8689

8790
// ACT
88-
logger.Log(LogLevel.Information, new Exception("TestException"), "TestMessage");
91+
logger.Log(LogLevel.Debug, new Exception("TestException"), "TestMessage");
8992

9093
// ASSERT
9194
Mock.Get(logger.RecordLine).Verify(invoke => invoke(
9295
It.Is<string>(s => s.Contains("TestMessage") && s.Contains("TestException")),
93-
It.Is<string[]>(p => p.Length == 0)), Times.Once);
96+
It.Is<object[]>(p => p.Length == 0)), Times.Once);
9497
}
9598

9699
[Fact]
97-
public void Log_auto_shows_LogDisplay()
100+
public void Log_includes_timestamp()
101+
{
102+
// ARRANGE
103+
var logger = new LogDisplayLogger("Test", new LogDisplayLoggerOptions
104+
{
105+
TimestampFormat = "yyyy-MM-dd HH:mm:ss ",
106+
})
107+
{
108+
RecordLine = Mock.Of<Action<string, object[]>>(),
109+
GetCurrentTimestamp = () => new DateTimeOffset(2023, 04, 17, 13, 34, 00, TimeSpan.Zero),
110+
};
111+
112+
// ACT
113+
logger.Log(LogLevel.Trace, new Exception("TestException"), "TestMessage");
114+
115+
// ASSERT
116+
Mock.Get(logger.RecordLine).Verify(invoke => invoke(
117+
It.Is<string>(s => s.Contains("2023-04-17 13:34:00")),
118+
It.Is<object[]>(p => p.Length == 0)), Times.Once);
119+
}
120+
121+
122+
[Theory]
123+
[InlineData(LogLevel.Error)]
124+
[InlineData(LogLevel.Critical)]
125+
public void Log_auto_shows_LogDisplay(LogLevel level)
98126
{
99127
// ARRANGE
100128
var logger = new LogDisplayLogger("Test")
101129
{
102-
RecordLine = Mock.Of<Action<string, string[]>>(),
130+
RecordLine = Mock.Of<Action<string, object[]>>(),
103131
Show = Mock.Of<Action>(),
104132
};
105133

106134
// ACT
107-
logger.Log(LogLevel.Error, new Exception("TestException"), "TestMessage");
135+
logger.Log(level, new Exception("TestException"), "TestMessage");
108136

109137
// ASSERT
110138
Mock.Get(logger.Show).Verify(invoke => invoke(), Times.Once);
111139
}
140+
141+
[Theory]
142+
[InlineData(LogLevel.Trace)]
143+
[InlineData(LogLevel.Debug)]
144+
[InlineData(LogLevel.Information)]
145+
[InlineData(LogLevel.Warning)]
146+
public void Log_does_not_show_LogDisplay(LogLevel level)
147+
{
148+
// ARRANGE
149+
var logger = new LogDisplayLogger("Test")
150+
{
151+
RecordLine = Mock.Of<Action<string, object[]>>(),
152+
Show = Mock.Of<Action>(),
153+
};
154+
155+
// ACT
156+
logger.Log(level, new Exception("TestException"), "TestMessage");
157+
158+
// ASSERT
159+
Mock.Get(logger.Show).Verify(invoke => invoke(), Times.Never);
160+
}
161+
162+
[Fact]
163+
public void Log_throws_when_logLevel_is_wrong()
164+
{
165+
// ARRANGE
166+
var logger = new LogDisplayLogger("Test");
167+
168+
// ACT & ASSERT
169+
Assert.Throws<ArgumentOutOfRangeException>(() =>
170+
{
171+
logger.Log((LogLevel)100, new Exception("TestException"), "TestMessage");
172+
});
173+
}
112174
}

Source/ExcelRna.Extensions.Logging/LogDisplayLogger.cs

+37-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Text;
23
using ExcelDna.Logging;
34
using Microsoft.Extensions.Logging;
45

@@ -22,20 +23,23 @@ public LogDisplayLogger(string name, LogDisplayLoggerOptions? options = null)
2223
Options = options ?? new LogDisplayLoggerOptions();
2324
}
2425

25-
internal Action<string, string[]> RecordLine { get; set; } = LogDisplay.RecordLine;
26+
internal Action<string, object[]> RecordLine { get; set; } = LogDisplay.RecordLine;
2627

2728
internal Action Show { get; set; } = LogDisplay.Show;
2829

2930
internal LogDisplayLoggerOptions Options { get; set; }
3031

32+
internal Func<DateTimeOffset> GetCurrentTimestamp { get; set; } = () => DateTimeOffset.Now;
33+
3134
/// <inheritdoc />
3235
public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None;
3336

3437
/// <inheritdoc />
3538
public IDisposable BeginScope<TState>(TState state) => NullScope.Instance;
3639

3740
/// <inheritdoc />
38-
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
41+
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception,
42+
Func<TState, Exception?, string> formatter)
3943
{
4044
if (!IsEnabled(logLevel))
4145
{
@@ -54,18 +58,46 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except
5458
return;
5559
}
5660

57-
message = $"{_name} [{logLevel}] {message}";
61+
StringBuilder builder = new();
62+
63+
if (Options.TimestampFormat != null)
64+
{
65+
DateTimeOffset dateTimeOffset = GetCurrentTimestamp();
66+
builder.Append(dateTimeOffset.ToString(Options.TimestampFormat));
67+
builder.Append(" ");
68+
}
69+
70+
builder.Append(GetLogLevelString(logLevel));
71+
builder.Append(": ");
72+
builder.Append(_name);
73+
builder.Append(" ");
74+
builder.Append(message);
5875

5976
if (exception != null)
6077
{
61-
message += Environment.NewLine + exception;
78+
builder.AppendLine();
79+
builder.Append(exception);
6280
}
6381

64-
RecordLine(message, Array.Empty<string>());
82+
RecordLine(builder.ToString(), Array.Empty<object>());
6583

6684
if (logLevel >= Options.AutoShowLogDisplayThreshold)
6785
{
6886
Show();
6987
}
7088
}
89+
90+
private static string GetLogLevelString(LogLevel logLevel)
91+
{
92+
return logLevel switch
93+
{
94+
LogLevel.Trace => "trce",
95+
LogLevel.Debug => "dbug",
96+
LogLevel.Information => "info",
97+
LogLevel.Warning => "warn",
98+
LogLevel.Error => "fail",
99+
LogLevel.Critical => "crit",
100+
_ => throw new ArgumentOutOfRangeException(nameof(logLevel))
101+
};
102+
}
71103
}

Source/ExcelRna.Extensions.Logging/LogDisplayLoggerOptions.cs

+5
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,9 @@ namespace ExcelRna.Extensions.Logging;
55
public class LogDisplayLoggerOptions
66
{
77
public LogLevel AutoShowLogDisplayThreshold { get; set; } = LogLevel.Error;
8+
9+
/// <summary>
10+
/// Gets or sets format string used to format timestamp in logging messages. Defaults to <c>null</c>.
11+
/// </summary>
12+
public string? TimestampFormat { get; set; }
813
}

Source/Samples/QuickStart/QuickStartAddIn.cs

+7-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,13 @@ public class QuickStartAddIn : HostedExcelAddIn
1919
protected override void AutoClose(IHost host) => IntelliSenseServer.Uninstall();
2020

2121
protected override IHostBuilder CreateHostBuilder() => Host.CreateDefaultBuilder()
22-
.ConfigureLogging(logging => { logging.AddLogDisplay(); })
22+
.ConfigureLogging(logging =>
23+
{
24+
logging.AddLogDisplay(options =>
25+
{
26+
options.TimestampFormat = "G";
27+
});
28+
})
2329
.ConfigureServices(services =>
2430
{
2531
services.AddTransient<IQuickStartService, QuickStartService>();

0 commit comments

Comments
 (0)