Skip to content

Commit 9cdd935

Browse files
authored
Fix logger property render bug (#255)
* Fix logger property render bug * Add comment
1 parent b6af3b1 commit 9cdd935

File tree

5 files changed

+92
-14
lines changed

5 files changed

+92
-14
lines changed

src/Akka.Logger.Serilog.Tests/LogMessageSpecs.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ public class LogMessageSpecs : TestKit.Xunit2.TestKit
1717
akka.loggers=[""Akka.Logger.Serilog.SerilogLogger, Akka.Logger.Serilog""]";
1818

1919
private readonly ILoggingAdapter _loggingAdapter;
20-
private readonly TestSink _sink = new TestSink();
20+
private readonly TestSink _sink;
2121

2222
public LogMessageSpecs(ITestOutputHelper helper) : base(Config, output: helper)
2323
{
24+
_sink = new TestSink(helper);
25+
2426
global::Serilog.Log.Logger = new LoggerConfiguration()
2527
.WriteTo.Sink(_sink)
2628
.MinimumLevel.Debug()

src/Akka.Logger.Serilog.Tests/SerilogFormattingSpecs.cs

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,75 @@
11
using System;
2+
using System.Collections.Generic;
23
using Akka.Actor;
34
using Akka.Configuration;
45
using Akka.Event;
56
using FluentAssertions;
67
using Serilog;
78
using Xunit;
89
using Xunit.Abstractions;
10+
using SerilogLog = Serilog.Log;
911

1012
namespace Akka.Logger.Serilog.Tests
1113
{
1214
public class SerilogFormattingSpecs : TestKit.Xunit2.TestKit
1315
{
14-
public static readonly Config Config = @"akka.loglevel = DEBUG";
15-
private ILogger _serilogLogger;
16+
public static readonly Config Config =
17+
@"
18+
akka.loglevel = DEBUG
19+
# akka.loggers=[""Akka.Logger.Serilog.SerilogLogger, Akka.Logger.Serilog""]
20+
";
21+
private readonly ILogger _serilogLogger;
22+
private readonly TestSink _sink;
1623

17-
private ILoggingAdapter _loggingAdapter;
24+
private readonly ILoggingAdapter _loggingAdapter;
1825

1926
public SerilogFormattingSpecs(ITestOutputHelper helper) : base(Config, output: helper)
2027
{
28+
_sink = new TestSink(helper);
29+
2130
_serilogLogger = new LoggerConfiguration()
2231
.WriteTo.ColoredConsole()
32+
.WriteTo.Sink(_sink)
2333
.MinimumLevel.Information()
2434
.CreateLogger();
35+
36+
SerilogLog.Logger = _serilogLogger;
2537

2638
var logSource = Sys.Name;
2739
var logClass = typeof(ActorSystem);
2840

2941
_loggingAdapter = new SerilogLoggingAdapter(Sys.EventStream, logSource, logClass);
3042
}
3143

44+
[Fact]
45+
public void LogOutputRegressionTest()
46+
{
47+
const string message = "{IntArray} {DoubleArray} {StringArray} {DoubleList}";
48+
const string expectedMessage = "[0, 1, 2] [0.1, 0.2, 0.3] [\"One\", \"Two\"] [1, 2, 3]";
49+
var args = new object[]
50+
{
51+
new int[] { 0, 1, 2 },
52+
new double[] { 0.1, 0.2, 0.3 },
53+
new string[] { "One", "Two" },
54+
new List<double> { 1, 2, 3 }
55+
};
56+
57+
_sink.Clear();
58+
AwaitCondition(() => _sink.Writes.Count == 0);
59+
60+
_serilogLogger.Information(message, args);
61+
AwaitCondition(() => _sink.Writes.Count == 1);
62+
63+
_sink.Writes.TryDequeue(out var logEvent).Should().BeTrue();
64+
logEvent.RenderMessage().Should().Be(expectedMessage);
65+
66+
Sys.EventStream.Subscribe(TestActor, typeof(LogEvent));
67+
_loggingAdapter.Log(LogLevel.InfoLevel, message, args);
68+
var akkaLogEvent = ExpectMsg<LogEvent>();
69+
70+
akkaLogEvent.ToString().Should().Contain(expectedMessage);
71+
}
72+
3273
[Theory]
3374
[InlineData(LogLevel.DebugLevel, "test case {0}", new object[]{ 1 })]
3475
[InlineData(LogLevel.DebugLevel, "test case {myNum}", new object[] { 1 })]

src/Akka.Logger.Serilog.Tests/TestSink.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Serilog.Core;
33
using Serilog.Events;
44
using Xunit;
5+
using Xunit.Abstractions;
56

67
[assembly: CollectionBehavior(DisableTestParallelization = true)]
78

@@ -15,6 +16,18 @@ public sealed class TestSink : ILogEventSink
1516
{
1617
public ConcurrentQueue<global::Serilog.Events.LogEvent> Writes { get; private set; } = new ConcurrentQueue<global::Serilog.Events.LogEvent>();
1718

19+
private readonly ITestOutputHelper _output;
20+
private int _count;
21+
22+
public TestSink(): this(null)
23+
{ }
24+
25+
public TestSink(ITestOutputHelper output)
26+
{
27+
_output = output;
28+
}
29+
30+
1831
/// <summary>
1932
/// Resets the contents of the queue
2033
/// </summary>
@@ -25,6 +38,8 @@ public void Clear()
2538

2639
public void Emit(global::Serilog.Events.LogEvent logEvent)
2740
{
41+
_count++;
42+
_output?.WriteLine($"[{nameof(TestSink)}][{_count}]: {logEvent.RenderMessage()}");
2843
Writes.Enqueue(logEvent);
2944
}
3045
}

src/Akka.Logger.Serilog/MessageTemplateCache.cs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
using System;
2-
using System.Collections.Generic;
2+
using System.Collections;
33
using Serilog.Events;
44
using Serilog.Parsing;
55

6+
#nullable enable
67
namespace Akka.Logger.Serilog
78
{
89
/// <summary>
@@ -12,10 +13,12 @@ namespace Akka.Logger.Serilog
1213
internal class MessageTemplateCache
1314
{
1415
private readonly MessageTemplateParser _innerParser;
15-
private readonly Dictionary<string, MessageTemplate> _templates = new Dictionary<string, MessageTemplate>();
16-
private readonly object _templatesLock = new object();
16+
private readonly object _templatesLock = new();
1717

18-
const int MaxCacheItems = 1000;
18+
private readonly Hashtable _templates = new();
19+
20+
private const int MaxCacheItems = 1000;
21+
private const int MaxCachedTemplateLength = 1024;
1922

2023
public MessageTemplateCache(MessageTemplateParser innerParser)
2124
{
@@ -26,10 +29,14 @@ public MessageTemplate Parse(string messageTemplate)
2629
{
2730
if (messageTemplate == null) throw new ArgumentNullException(nameof(messageTemplate));
2831

29-
MessageTemplate result;
30-
lock (_templatesLock)
31-
if (_templates.TryGetValue(messageTemplate, out result))
32-
return result;
32+
if (messageTemplate.Length > MaxCachedTemplateLength)
33+
return _innerParser.Parse(messageTemplate);
34+
35+
// ReSharper disable once InconsistentlySynchronizedField
36+
// ignored warning because this is by design
37+
var result = (MessageTemplate?)_templates[messageTemplate];
38+
if (result != null)
39+
return result;
3340

3441
result = _innerParser.Parse(messageTemplate);
3542

@@ -39,8 +46,14 @@ public MessageTemplate Parse(string messageTemplate)
3946
// conditions when the library is used incorrectly. Correct use (templates, rather than
4047
// direct message strings) should barely, if ever, overflow this cache.
4148

42-
if (_templates.Count <= MaxCacheItems)
43-
_templates[messageTemplate] = result;
49+
// Changing workloads through the lifecycle of an app instance mean we can gain some ground by
50+
// potentially dropping templates generated only in startup, or only during specific infrequent
51+
// activities.
52+
53+
if (_templates.Count == MaxCacheItems)
54+
_templates.Clear();
55+
56+
_templates[messageTemplate] = result;
4457
}
4558

4659
return result;

src/Akka.Logger.Serilog/SerilogLogMessageFormatter.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Collections.Generic;
22
using System.Linq;
33
using Akka.Event;
4+
using Serilog;
45
using Serilog.Events;
56
using Serilog.Parsing;
67

@@ -37,6 +38,12 @@ public SerilogLogMessageFormatter()
3738
/// </returns>
3839
public string Format(string format, params object[] args)
3940
{
41+
// We're using serilog built-in function to bind all the object arguments to their proper
42+
// value types. If this fails, we fell back to the old implementation where we map everything
43+
// to ScalarValue.
44+
if (Log.Logger.BindMessageTemplate(format, args, out var boundTemplate, out var boundProps))
45+
return boundTemplate.Render(boundProps.ToDictionary(p => p.Name, p => p.Value));
46+
4047
var template = _templateCache.Parse(format);
4148
var propertyTokens = template.Tokens.OfType<PropertyToken>().ToArray();
4249
var properties = new Dictionary<string, LogEventPropertyValue>();

0 commit comments

Comments
 (0)