Skip to content

Commit 11c3c07

Browse files
authored
Merge pull request #142 from nblumhardt/event-id-caching
Cache low-numbered numeric event ids to reduce allocations
2 parents e1a4780 + 40a2a2d commit 11c3c07

File tree

7 files changed

+251
-109
lines changed

7 files changed

+251
-109
lines changed

serilog-extensions-logging.sln

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio 15
4-
VisualStudioVersion = 15.0.26730.10
3+
# Visual Studio Version 16
4+
VisualStudioVersion = 16.0.29209.62
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A1893BD1-333D-4DFE-A0F0-DDBB2FE526E0}"
77
EndProject
@@ -24,6 +24,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{9C21B9
2424
assets\Serilog.snk = assets\Serilog.snk
2525
EndProjectSection
2626
EndProject
27+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Extensions.Logging.Benchmarks", "test\Serilog.Extensions.Logging.Benchmarks\Serilog.Extensions.Logging.Benchmarks.csproj", "{6D5986FF-EECD-4E75-8BC6-A5F78AB549B2}"
28+
EndProject
2729
Global
2830
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2931
Debug|Any CPU = Debug|Any CPU
@@ -42,6 +44,10 @@ Global
4244
{65357FBC-9BC4-466D-B621-1C3A19BC2A78}.Debug|Any CPU.Build.0 = Debug|Any CPU
4345
{65357FBC-9BC4-466D-B621-1C3A19BC2A78}.Release|Any CPU.ActiveCfg = Release|Any CPU
4446
{65357FBC-9BC4-466D-B621-1C3A19BC2A78}.Release|Any CPU.Build.0 = Release|Any CPU
47+
{6D5986FF-EECD-4E75-8BC6-A5F78AB549B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
48+
{6D5986FF-EECD-4E75-8BC6-A5F78AB549B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
49+
{6D5986FF-EECD-4E75-8BC6-A5F78AB549B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
50+
{6D5986FF-EECD-4E75-8BC6-A5F78AB549B2}.Release|Any CPU.Build.0 = Release|Any CPU
4551
EndGlobalSection
4652
GlobalSection(SolutionProperties) = preSolution
4753
HideSolutionNode = FALSE
@@ -50,6 +56,7 @@ Global
5056
{903CD13A-D54B-4CEC-A55F-E22AE3D93B3B} = {A1893BD1-333D-4DFE-A0F0-DDBB2FE526E0}
5157
{37EADF84-5E41-4224-A194-1E3299DCD0B8} = {E30F638E-BBBE-4AD1-93CE-48CC69CFEFE1}
5258
{65357FBC-9BC4-466D-B621-1C3A19BC2A78} = {F2407211-6043-439C-8E06-3641634332E7}
59+
{6D5986FF-EECD-4E75-8BC6-A5F78AB549B2} = {E30F638E-BBBE-4AD1-93CE-48CC69CFEFE1}
5360
EndGlobalSection
5461
GlobalSection(ExtensibilityGlobals) = postSolution
5562
SolutionGuid = {811E61C5-3871-4633-AFAE-B35B619C8A10}
+5-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
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">
22
<s:Boolean x:Key="/Default/UserDictionary/Words/=destructure/@EntryIndexedValue">True</s:Boolean>
3+
<s:Boolean x:Key="/Default/UserDictionary/Words/=destructured/@EntryIndexedValue">True</s:Boolean>
34
<s:Boolean x:Key="/Default/UserDictionary/Words/=Destructurer/@EntryIndexedValue">True</s:Boolean>
5+
<s:Boolean x:Key="/Default/UserDictionary/Words/=Destructures/@EntryIndexedValue">True</s:Boolean>
46
<s:Boolean x:Key="/Default/UserDictionary/Words/=enricher/@EntryIndexedValue">True</s:Boolean>
57
<s:Boolean x:Key="/Default/UserDictionary/Words/=enrichers/@EntryIndexedValue">True</s:Boolean>
8+
<s:Boolean x:Key="/Default/UserDictionary/Words/=Loggable/@EntryIndexedValue">True</s:Boolean>
69
<s:Boolean x:Key="/Default/UserDictionary/Words/=Nonscalar/@EntryIndexedValue">True</s:Boolean>
7-
<s:Boolean x:Key="/Default/UserDictionary/Words/=Serilog/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
10+
<s:Boolean x:Key="/Default/UserDictionary/Words/=Serilog/@EntryIndexedValue">True</s:Boolean>
11+
<s:Boolean x:Key="/Default/UserDictionary/Words/=sobj/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

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

+22-18
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Microsoft.Extensions.Logging;
55
using System;
66
using System.Collections.Generic;
7+
using System.Linq;
78
using Serilog.Core;
89
using Serilog.Events;
910
using FrameworkLogger = Microsoft.Extensions.Logging.ILogger;
@@ -17,15 +18,19 @@ class SerilogLogger : FrameworkLogger
1718
readonly SerilogLoggerProvider _provider;
1819
readonly ILogger _logger;
1920

20-
static readonly MessageTemplateParser _messageTemplateParser = new MessageTemplateParser();
21+
static readonly MessageTemplateParser MessageTemplateParser = new MessageTemplateParser();
22+
23+
// It's rare to see large event ids, as they are category-specific
24+
static readonly LogEventProperty[] LowEventIdValues = Enumerable.Range(0, 48)
25+
.Select(n => new LogEventProperty("Id", new ScalarValue(n)))
26+
.ToArray();
2127

2228
public SerilogLogger(
2329
SerilogLoggerProvider provider,
2430
ILogger logger = null,
2531
string name = null)
2632
{
27-
if (provider == null) throw new ArgumentNullException(nameof(provider));
28-
_provider = provider;
33+
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
2934
_logger = logger;
3035

3136
// If a logger was passed, the provider has already added itself as an enricher
@@ -60,25 +65,22 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except
6065

6166
var properties = new List<LogEventProperty>();
6267

63-
var structure = state as IEnumerable<KeyValuePair<string, object>>;
64-
if (structure != null)
68+
if (state is IEnumerable<KeyValuePair<string, object>> structure)
6569
{
6670
foreach (var property in structure)
6771
{
68-
if (property.Key == SerilogLoggerProvider.OriginalFormatPropertyName && property.Value is string)
72+
if (property.Key == SerilogLoggerProvider.OriginalFormatPropertyName && property.Value is string value)
6973
{
70-
messageTemplate = (string)property.Value;
74+
messageTemplate = value;
7175
}
7276
else if (property.Key.StartsWith("@"))
7377
{
74-
LogEventProperty destructured;
75-
if (logger.BindProperty(property.Key.Substring(1), property.Value, true, out destructured))
78+
if (logger.BindProperty(property.Key.Substring(1), property.Value, true, out var destructured))
7679
properties.Add(destructured);
7780
}
7881
else
7982
{
80-
LogEventProperty bound;
81-
if (logger.BindProperty(property.Key, property.Value, false, out bound))
83+
if (logger.BindProperty(property.Key, property.Value, false, out var bound))
8284
properties.Add(bound);
8385
}
8486
}
@@ -89,8 +91,7 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except
8991
if (messageTemplate == null && !stateTypeInfo.IsGenericType)
9092
{
9193
messageTemplate = "{" + stateType.Name + ":l}";
92-
LogEventProperty stateTypeProperty;
93-
if (logger.BindProperty(stateType.Name, AsLoggableValue(state, formatter), false, out stateTypeProperty))
94+
if (logger.BindProperty(stateType.Name, AsLoggableValue(state, formatter), false, out var stateTypeProperty))
9495
properties.Add(stateTypeProperty);
9596
}
9697
}
@@ -111,16 +112,15 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except
111112

112113
if (propertyName != null)
113114
{
114-
LogEventProperty property;
115-
if (logger.BindProperty(propertyName, AsLoggableValue(state, formatter), false, out property))
115+
if (logger.BindProperty(propertyName, AsLoggableValue(state, formatter), false, out var property))
116116
properties.Add(property);
117117
}
118118
}
119119

120120
if (eventId.Id != 0 || eventId.Name != null)
121121
properties.Add(CreateEventIdProperty(eventId));
122122

123-
var parsedTemplate = _messageTemplateParser.Parse(messageTemplate ?? "");
123+
var parsedTemplate = MessageTemplateParser.Parse(messageTemplate ?? "");
124124
var evt = new LogEvent(DateTimeOffset.Now, level, exception, parsedTemplate, properties);
125125
logger.Write(evt);
126126
}
@@ -133,13 +133,17 @@ static object AsLoggableValue<TState>(TState state, Func<TState, Exception, stri
133133
return sobj;
134134
}
135135

136-
static LogEventProperty CreateEventIdProperty(EventId eventId)
136+
internal static LogEventProperty CreateEventIdProperty(EventId eventId)
137137
{
138138
var properties = new List<LogEventProperty>(2);
139139

140140
if (eventId.Id != 0)
141141
{
142-
properties.Add(new LogEventProperty("Id", new ScalarValue(eventId.Id)));
142+
if (eventId.Id < LowEventIdValues.Length)
143+
// Avoid some allocations
144+
properties.Add(LowEventIdValues[eventId.Id]);
145+
else
146+
properties.Add(new LogEventProperty("Id", new ScalarValue(eventId.Id)));
143147
}
144148

145149
if (eventId.Name != null)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright 2019 Serilog Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using System;
16+
using BenchmarkDotNet.Attributes;
17+
using BenchmarkDotNet.Running;
18+
using Microsoft.Extensions.Logging;
19+
using IMelLogger = Microsoft.Extensions.Logging.ILogger;
20+
using Serilog.Events;
21+
using Serilog.Extensions.Logging.Benchmarks.Support;
22+
using Xunit;
23+
24+
namespace Serilog.Extensions.Logging.Benchmarks
25+
{
26+
[MemoryDiagnoser]
27+
public class LogEventConstructionBenchmark
28+
{
29+
readonly IMelLogger _melLogger;
30+
readonly ILogger _serilogContextualLogger;
31+
readonly CapturingSink _sink;
32+
const int LowId = 10, HighId = 101;
33+
const string Template = "This is an event";
34+
35+
public LogEventConstructionBenchmark()
36+
{
37+
_sink = new CapturingSink();
38+
var underlyingLogger = new LoggerConfiguration().WriteTo.Sink(_sink).CreateLogger();
39+
_serilogContextualLogger = underlyingLogger.ForContext<LogEventConstructionBenchmark>();
40+
_melLogger = new SerilogLoggerProvider(underlyingLogger).CreateLogger(GetType().FullName);
41+
}
42+
43+
static void VerifyEventId(LogEvent evt, int? expectedId)
44+
{
45+
if (evt == null) throw new ArgumentNullException(nameof(evt));
46+
if (expectedId == null)
47+
{
48+
Assert.False(evt.Properties.TryGetValue("EventId", out _));
49+
}
50+
else
51+
{
52+
Assert.True(evt.Properties.TryGetValue("EventId", out var eventIdValue));
53+
var structure = Assert.IsType<StructureValue>(eventIdValue);
54+
var idValue = Assert.Single(structure.Properties, p => p.Name == "Id")?.Value;
55+
var scalar = Assert.IsType<ScalarValue>(idValue);
56+
Assert.Equal(expectedId.Value, scalar.Value);
57+
}
58+
}
59+
60+
[Fact]
61+
public void Verify()
62+
{
63+
VerifyEventId(Native(), null);
64+
VerifyEventId(NoId(), null);
65+
VerifyEventId(LowNumbered(), LowId);
66+
VerifyEventId(HighNumbered(), HighId);
67+
}
68+
69+
[Fact]
70+
public void Benchmark()
71+
{
72+
BenchmarkRunner.Run<LogEventConstructionBenchmark>();
73+
}
74+
75+
[Benchmark(Baseline = true)]
76+
public LogEvent Native()
77+
{
78+
_serilogContextualLogger.Information(Template);
79+
return _sink.Collect();
80+
}
81+
82+
[Benchmark]
83+
public LogEvent NoId()
84+
{
85+
_melLogger.LogInformation(Template);
86+
return _sink.Collect();
87+
}
88+
89+
[Benchmark]
90+
public LogEvent LowNumbered()
91+
{
92+
_melLogger.LogInformation(LowId, Template);
93+
return _sink.Collect();
94+
}
95+
96+
[Benchmark]
97+
public LogEvent HighNumbered()
98+
{
99+
_melLogger.LogInformation(HighId, Template);
100+
return _sink.Collect();
101+
}
102+
}
103+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>netcoreapp2.2</TargetFrameworks>
5+
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<ProjectReference Include="..\..\src\Serilog.Extensions.Logging\Serilog.Extensions.Logging.csproj" />
10+
</ItemGroup>
11+
12+
<ItemGroup>
13+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.1.1" />
14+
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
15+
<PrivateAssets>all</PrivateAssets>
16+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
17+
</PackageReference>
18+
<PackageReference Include="xunit" Version="2.4.1" />
19+
<PackageReference Include="BenchmarkDotNet" Version="0.11.5" />
20+
</ItemGroup>
21+
22+
<ItemGroup>
23+
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
24+
</ItemGroup>
25+
26+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2019 Serilog Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using Serilog.Core;
16+
using Serilog.Events;
17+
18+
namespace Serilog.Extensions.Logging.Benchmarks.Support
19+
{
20+
class CapturingSink : ILogEventSink
21+
{
22+
LogEvent _emitted;
23+
24+
public void Emit(LogEvent logEvent)
25+
{
26+
_emitted = logEvent;
27+
}
28+
29+
public LogEvent Collect()
30+
{
31+
var collected = _emitted;
32+
_emitted = null;
33+
return collected;
34+
}
35+
}
36+
}

0 commit comments

Comments
 (0)