Skip to content

Commit 92751dd

Browse files
authored
Merge pull request #247 from nblumhardt/serilog-4x
Remove some allocations
2 parents 1549fe5 + e47a264 commit 92751dd

File tree

10 files changed

+193
-140
lines changed

10 files changed

+193
-140
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2+
<s:Boolean x:Key="/Default/UserDictionary/Words/=enricher/@EntryIndexedValue">True</s:Boolean>
23
<s:Boolean x:Key="/Default/UserDictionary/Words/=stringified/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

src/Serilog.Extensions.Logging/Extensions/Logging/EventIdPropertyCache.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ namespace Serilog.Extensions.Logging;
2121
sealed class EventIdPropertyCache
2222
{
2323
readonly int _maxCachedProperties;
24-
readonly ConcurrentDictionary<EventKey, LogEventProperty> _propertyCache = new();
24+
readonly ConcurrentDictionary<EventKey, LogEventPropertyValue> _propertyCache = new();
2525

2626
int _count;
2727

@@ -30,32 +30,32 @@ public EventIdPropertyCache(int maxCachedProperties = 1024)
3030
_maxCachedProperties = maxCachedProperties;
3131
}
3232

33-
public LogEventProperty GetOrCreateProperty(in EventId eventId)
33+
public LogEventPropertyValue GetOrCreatePropertyValue(in EventId eventId)
3434
{
3535
var eventKey = new EventKey(eventId);
3636

37-
LogEventProperty? property;
37+
LogEventPropertyValue? propertyValue;
3838

3939
if (_count >= _maxCachedProperties)
4040
{
41-
if (!_propertyCache.TryGetValue(eventKey, out property))
41+
if (!_propertyCache.TryGetValue(eventKey, out propertyValue))
4242
{
43-
property = CreateProperty(in eventKey);
43+
propertyValue = CreatePropertyValue(in eventKey);
4444
}
4545
}
4646
else
4747
{
48-
if (!_propertyCache.TryGetValue(eventKey, out property))
48+
if (!_propertyCache.TryGetValue(eventKey, out propertyValue))
4949
{
5050
// GetOrAdd is moved to a separate method to prevent closure allocation
51-
property = GetOrAddCore(in eventKey);
51+
propertyValue = GetOrAddCore(in eventKey);
5252
}
5353
}
5454

55-
return property;
55+
return propertyValue;
5656
}
5757

58-
static LogEventProperty CreateProperty(in EventKey eventKey)
58+
static LogEventPropertyValue CreatePropertyValue(in EventKey eventKey)
5959
{
6060
var properties = new List<LogEventProperty>(2);
6161

@@ -69,17 +69,17 @@ static LogEventProperty CreateProperty(in EventKey eventKey)
6969
properties.Add(new LogEventProperty("Name", new ScalarValue(eventKey.Name)));
7070
}
7171

72-
return new LogEventProperty("EventId", new StructureValue(properties));
72+
return new StructureValue(properties);
7373
}
7474

75-
LogEventProperty GetOrAddCore(in EventKey eventKey) =>
75+
LogEventPropertyValue GetOrAddCore(in EventKey eventKey) =>
7676
_propertyCache.GetOrAdd(
7777
eventKey,
7878
key =>
7979
{
8080
Interlocked.Increment(ref _count);
8181

82-
return CreateProperty(in key);
82+
return CreatePropertyValue(in key);
8383
});
8484

8585
readonly record struct EventKey

src/Serilog.Extensions.Logging/Extensions/Logging/LevelConvert.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ public static LogEventLevel ToSerilogLevel(LogLevel logLevel)
3636
{
3737
return logLevel switch
3838
{
39-
LogLevel.None or LogLevel.Critical => LogEventLevel.Fatal,
39+
LogLevel.None => LevelAlias.Off,
40+
LogLevel.Critical => LogEventLevel.Fatal,
4041
LogLevel.Error => LogEventLevel.Error,
4142
LogLevel.Warning => LogEventLevel.Warning,
4243
LogLevel.Information => LogEventLevel.Information,

src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -89,30 +89,33 @@ LogEvent PrepareWrite<TState>(LogEventLevel level, EventId eventId, TState state
8989
{
9090
string? messageTemplate = null;
9191

92-
var properties = new List<LogEventProperty>();
92+
var properties = new Dictionary<string, LogEventPropertyValue>();
9393

9494
if (state is IEnumerable<KeyValuePair<string, object>> structure)
9595
{
9696
foreach (var property in structure)
9797
{
98-
if (property.Key == SerilogLoggerProvider.OriginalFormatPropertyName && property.Value is string value)
98+
if (property is { Key: SerilogLoggerProvider.OriginalFormatPropertyName, Value: string value })
9999
{
100100
messageTemplate = value;
101101
}
102102
else if (property.Key.StartsWith('@'))
103103
{
104104
if (_logger.BindProperty(GetKeyWithoutFirstSymbol(DestructureDictionary, property.Key), property.Value, true, out var destructured))
105-
properties.Add(destructured);
105+
properties[destructured.Name] = destructured.Value;
106106
}
107107
else if (property.Key.StartsWith('$'))
108108
{
109109
if (_logger.BindProperty(GetKeyWithoutFirstSymbol(StringifyDictionary, property.Key), property.Value?.ToString(), true, out var stringified))
110-
properties.Add(stringified);
110+
properties[stringified.Name] = stringified.Value;
111111
}
112112
else
113113
{
114-
if (_logger.BindProperty(property.Key, property.Value, false, out var bound))
115-
properties.Add(bound);
114+
// Simple micro-optimization for the most common and reliably scalar values; could go further here.
115+
if (property.Value is null or string or int or long && LogEventProperty.IsValidName(property.Key))
116+
properties[property.Key] = new ScalarValue(property.Value);
117+
else if (_logger.BindProperty(property.Key, property.Value, false, out var bound))
118+
properties[bound.Name] = bound.Value;
116119
}
117120
}
118121

@@ -123,7 +126,7 @@ LogEvent PrepareWrite<TState>(LogEventLevel level, EventId eventId, TState state
123126
{
124127
messageTemplate = "{" + stateType.Name + ":l}";
125128
if (_logger.BindProperty(stateType.Name, AsLoggableValue(state, formatter), false, out var stateTypeProperty))
126-
properties.Add(stateTypeProperty);
129+
properties[stateTypeProperty.Name] = stateTypeProperty.Value;
127130
}
128131
}
129132

@@ -146,19 +149,20 @@ LogEvent PrepareWrite<TState>(LogEventLevel level, EventId eventId, TState state
146149
if (propertyName != null)
147150
{
148151
if (_logger.BindProperty(propertyName, AsLoggableValue(state, formatter!), false, out var property))
149-
properties.Add(property);
152+
properties[property.Name] = property.Value;
150153
}
151154
}
152155

156+
// The overridden `!=` operator on this type ignores `Name`.
153157
if (eventId.Id != 0 || eventId.Name != null)
154-
properties.Add(_eventIdPropertyCache.GetOrCreateProperty(in eventId));
158+
properties.Add("EventId", _eventIdPropertyCache.GetOrCreatePropertyValue(in eventId));
155159

156160
var (traceId, spanId) = Activity.Current is { } activity ?
157161
(activity.TraceId, activity.SpanId) :
158162
(default(ActivityTraceId), default(ActivitySpanId));
159163

160-
var parsedTemplate = MessageTemplateParser.Parse(messageTemplate ?? "");
161-
return new LogEvent(DateTimeOffset.Now, level, exception, parsedTemplate, properties, traceId, spanId);
164+
var parsedTemplate = messageTemplate != null ? MessageTemplateParser.Parse(messageTemplate) : MessageTemplate.Empty;
165+
return LogEvent.UnstableAssembleFromParts(DateTimeOffset.Now, level, exception, parsedTemplate, properties, traceId, spanId);
162166
}
163167

164168
static object? AsLoggableValue<TState>(TState state, Func<TState, Exception?, string>? formatter)

src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs

Lines changed: 34 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -51,25 +51,6 @@ public void Dispose()
5151

5252
public static void EnrichWithStateAndCreateScopeItem(LogEvent logEvent, ILogEventPropertyFactory propertyFactory, object? state, out LogEventPropertyValue? scopeItem)
5353
{
54-
void AddProperty(string key, object? value)
55-
{
56-
var destructureObject = false;
57-
58-
if (key.StartsWith('@'))
59-
{
60-
key = SerilogLogger.GetKeyWithoutFirstSymbol(SerilogLogger.DestructureDictionary, key);
61-
destructureObject = true;
62-
}
63-
else if (key.StartsWith('$'))
64-
{
65-
key = SerilogLogger.GetKeyWithoutFirstSymbol(SerilogLogger.StringifyDictionary, key);
66-
value = value?.ToString();
67-
}
68-
69-
var property = propertyFactory.CreateProperty(key, value, destructureObject);
70-
logEvent.AddPropertyIfAbsent(property);
71-
}
72-
7354
if (state == null)
7455
{
7556
scopeItem = null;
@@ -79,52 +60,64 @@ void AddProperty(string key, object? value)
7960
// Eliminates boxing of Dictionary<TKey, TValue>.Enumerator for the most common use case
8061
if (state is Dictionary<string, object> dictionary)
8162
{
82-
scopeItem = null; // Unless it's `FormattedLogValues`, these are treated as property bags rather than scope items.
83-
63+
// Separate handling of this case eliminates boxing of Dictionary<TKey, TValue>.Enumerator.
64+
scopeItem = null;
8465
foreach (var stateProperty in dictionary)
85-
{
86-
if (stateProperty.Key == SerilogLoggerProvider.OriginalFormatPropertyName && stateProperty.Value is string)
87-
scopeItem = new ScalarValue(state.ToString());
88-
else
89-
AddProperty(stateProperty.Key, stateProperty.Value);
66+
{
67+
AddProperty(logEvent, propertyFactory, stateProperty.Key, stateProperty.Value);
9068
}
9169
}
9270
else if (state is IEnumerable<KeyValuePair<string, object>> stateProperties)
9371
{
94-
scopeItem = null; // Unless it's `FormattedLogValues`, these are treated as property bags rather than scope items.
95-
72+
scopeItem = null;
9673
foreach (var stateProperty in stateProperties)
9774
{
98-
if (stateProperty.Key == SerilogLoggerProvider.OriginalFormatPropertyName && stateProperty.Value is string)
75+
if (stateProperty is { Key: SerilogLoggerProvider.OriginalFormatPropertyName, Value: string })
76+
{
77+
// `_state` is most likely `FormattedLogValues` (a MEL internal type).
9978
scopeItem = new ScalarValue(state.ToString());
79+
}
10080
else
101-
AddProperty(stateProperty.Key, stateProperty.Value);
81+
{
82+
AddProperty(logEvent, propertyFactory, stateProperty.Key, stateProperty.Value);
83+
}
10284
}
10385
}
10486
#if FEATURE_ITUPLE
10587
else if (state is System.Runtime.CompilerServices.ITuple tuple && tuple.Length == 2 && tuple[0] is string s)
10688
{
10789
scopeItem = null; // Unless it's `FormattedLogValues`, these are treated as property bags rather than scope items.
108-
109-
if (s == SerilogLoggerProvider.OriginalFormatPropertyName && tuple[1] is string)
110-
scopeItem = new ScalarValue(state.ToString());
111-
else
112-
AddProperty(s, tuple[1]);
90+
AddProperty(logEvent, propertyFactory, s, tuple[1]);
11391
}
11492
#else
11593
else if (state is ValueTuple<string, object?> tuple)
11694
{
117-
scopeItem = null; // Unless it's `FormattedLogValues`, these are treated as property bags rather than scope items.
118-
119-
if (tuple.Item1 == SerilogLoggerProvider.OriginalFormatPropertyName && tuple.Item2 is string)
120-
scopeItem = new ScalarValue(state.ToString());
121-
else
122-
AddProperty(tuple.Item1, tuple.Item2);
95+
scopeItem = null;
96+
AddProperty(logEvent, propertyFactory, tuple.Item1, tuple.Item2);
12397
}
12498
#endif
12599
else
126100
{
127101
scopeItem = propertyFactory.CreateProperty(NoName, state).Value;
128102
}
129103
}
104+
105+
static void AddProperty(LogEvent logEvent, ILogEventPropertyFactory propertyFactory, string key, object? value)
106+
{
107+
var destructureObject = false;
108+
109+
if (key.StartsWith('@'))
110+
{
111+
key = SerilogLogger.GetKeyWithoutFirstSymbol(SerilogLogger.DestructureDictionary, key);
112+
destructureObject = true;
113+
}
114+
else if (key.StartsWith('$'))
115+
{
116+
key = SerilogLogger.GetKeyWithoutFirstSymbol(SerilogLogger.StringifyDictionary, key);
117+
value = value?.ToString();
118+
}
119+
120+
var property = propertyFactory.CreateProperty(key, value, destructureObject);
121+
logEvent.AddPropertyIfAbsent(property);
122+
}
130123
}

src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
<ItemGroup>
2121
<None Include="..\..\assets\serilog-extension-nuget.png" Pack="true" PackagePath="" Visible="false" />
2222
<None Include="..\..\README.md" Pack="true" PackagePath="\" Visible="false" />
23-
<PackageReference Include="Serilog" Version="4.2.0-*" />
23+
<PackageReference Include="Serilog" Version="4.2.0" />
2424
<!-- The version of this reference must match the major and minor components of the package version prefix. -->
2525
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />
2626
<PackageReference Include="Nullable" Version="1.3.1" PrivateAssets="All" />

test/Serilog.Extensions.Logging.Benchmarks/LogEventConstructionBenchmark.cs renamed to test/Serilog.Extensions.Logging.Benchmarks/EventIdCapturingBenchmark.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,19 @@
2323
namespace Serilog.Extensions.Logging.Benchmarks;
2424

2525
[MemoryDiagnoser]
26-
public class LogEventConstructionBenchmark
26+
public class EventIdCapturingBenchmark
2727
{
2828
readonly IMelLogger _melLogger;
2929
readonly ILogger _serilogContextualLogger;
3030
readonly CapturingSink _sink;
3131
const int LowId = 10, HighId = 101;
3232
const string Template = "This is an event";
3333

34-
public LogEventConstructionBenchmark()
34+
public EventIdCapturingBenchmark()
3535
{
3636
_sink = new CapturingSink();
3737
var underlyingLogger = new LoggerConfiguration().WriteTo.Sink(_sink).CreateLogger();
38-
_serilogContextualLogger = underlyingLogger.ForContext<LogEventConstructionBenchmark>();
38+
_serilogContextualLogger = underlyingLogger.ForContext<EventIdCapturingBenchmark>();
3939
_melLogger = new SerilogLoggerProvider(underlyingLogger).CreateLogger(GetType().FullName!);
4040
}
4141

@@ -68,7 +68,7 @@ public void Verify()
6868
[Fact]
6969
public void Benchmark()
7070
{
71-
BenchmarkRunner.Run<LogEventConstructionBenchmark>();
71+
BenchmarkRunner.Run<EventIdCapturingBenchmark>();
7272
}
7373

7474
[Benchmark(Baseline = true)]

0 commit comments

Comments
 (0)