Skip to content

Commit d815c3c

Browse files
committed
Use IReadOnlyList type-matching to avoid enumerator allocation.
1 parent d8c75ec commit d815c3c

File tree

1 file changed

+55
-31
lines changed

1 file changed

+55
-31
lines changed

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

Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -91,43 +91,27 @@ LogEvent PrepareWrite<TState>(LogEventLevel level, EventId eventId, TState state
9191

9292
var properties = new Dictionary<string, LogEventPropertyValue>();
9393

94-
if (state is IEnumerable<KeyValuePair<string, object?>> structure)
94+
// Optimization: MEL state object type represents either LogValues or FormattedLogValues
95+
// These types implement IReadOnlyList, which be used to avoid enumerator obj allocation.
96+
if (state is IReadOnlyList<KeyValuePair<string, object?>> propertiesList)
9597
{
96-
foreach (var property in structure)
98+
var length = propertiesList.Count;
99+
100+
for (var i = 0; i < length; i++)
97101
{
98-
if (property is { Key: SerilogLoggerProvider.OriginalFormatPropertyName, Value: string value })
99-
{
100-
messageTemplate = value;
101-
}
102-
else if (property.Key.StartsWith('@'))
103-
{
104-
if (_logger.BindProperty(GetKeyWithoutFirstSymbol(DestructureDictionary, property.Key), property.Value, true, out var destructured))
105-
properties[destructured.Name] = destructured.Value;
106-
}
107-
else if (property.Key.StartsWith('$'))
108-
{
109-
if (_logger.BindProperty(GetKeyWithoutFirstSymbol(StringifyDictionary, property.Key), property.Value?.ToString(), true, out var stringified))
110-
properties[stringified.Name] = stringified.Value;
111-
}
112-
else
113-
{
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;
119-
}
102+
ProcessStateProperty(propertiesList[i]);
120103
}
121104

122-
var stateType = state.GetType();
123-
var stateTypeInfo = stateType.GetTypeInfo();
124-
// Imperfect, but at least eliminates `1 names
125-
if (messageTemplate == null && !stateTypeInfo.IsGenericType)
105+
TrySetMessageTemplateFromState();
106+
}
107+
else if (state is IEnumerable<KeyValuePair<string, object?>> propertiesEnumerable)
108+
{
109+
foreach (var property in propertiesEnumerable)
126110
{
127-
messageTemplate = "{" + stateType.Name + ":l}";
128-
if (_logger.BindProperty(stateType.Name, AsLoggableValue(state, formatter), false, out var stateTypeProperty))
129-
properties[stateTypeProperty.Name] = stateTypeProperty.Value;
111+
ProcessStateProperty(property);
130112
}
113+
114+
TrySetMessageTemplateFromState();
131115
}
132116

133117
if (messageTemplate == null)
@@ -163,6 +147,46 @@ LogEvent PrepareWrite<TState>(LogEventLevel level, EventId eventId, TState state
163147

164148
var parsedTemplate = messageTemplate != null ? MessageTemplateParser.Parse(messageTemplate) : MessageTemplate.Empty;
165149
return LogEvent.UnstableAssembleFromParts(DateTimeOffset.Now, level, exception, parsedTemplate, properties, traceId, spanId);
150+
151+
void ProcessStateProperty(KeyValuePair<string, object?> property)
152+
{
153+
if (property is { Key: SerilogLoggerProvider.OriginalFormatPropertyName, Value: string value })
154+
{
155+
messageTemplate = value;
156+
}
157+
else if (property.Key.StartsWith('@'))
158+
{
159+
if (this._logger.BindProperty(GetKeyWithoutFirstSymbol(DestructureDictionary, property.Key), property.Value, true, out var destructured))
160+
properties[destructured.Name] = destructured.Value;
161+
}
162+
else if (property.Key.StartsWith('$'))
163+
{
164+
if (this._logger.BindProperty(GetKeyWithoutFirstSymbol(StringifyDictionary, property.Key), property.Value?.ToString(), true, out var stringified))
165+
properties[stringified.Name] = stringified.Value;
166+
}
167+
else
168+
{
169+
// Simple micro-optimization for the most common and reliably scalar values; could go further here.
170+
if (property.Value is null or string or int or long && LogEventProperty.IsValidName(property.Key))
171+
properties[property.Key] = new ScalarValue(property.Value);
172+
else if (this._logger.BindProperty(property.Key, property.Value, false, out var bound))
173+
properties[bound.Name] = bound.Value;
174+
}
175+
}
176+
177+
void TrySetMessageTemplateFromState()
178+
{
179+
// Imperfect, but at least eliminates `1 names
180+
var stateType = state.GetType();
181+
var stateTypeInfo = stateType.GetTypeInfo();
182+
// Imperfect, but at least eliminates `1 names
183+
if (messageTemplate == null && !stateTypeInfo.IsGenericType)
184+
{
185+
messageTemplate = "{" + stateType.Name + ":l}";
186+
if (_logger.BindProperty(stateType.Name, AsLoggableValue(state, formatter), false, out var stateTypeProperty))
187+
properties[stateTypeProperty.Name] = stateTypeProperty.Value;
188+
}
189+
}
166190
}
167191

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

0 commit comments

Comments
 (0)