@@ -91,43 +91,27 @@ LogEvent PrepareWrite<TState>(LogEventLevel level, EventId eventId, TState state
91
91
92
92
var properties = new Dictionary < string , LogEventPropertyValue > ( ) ;
93
93
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 )
95
97
{
96
- foreach ( var property in structure )
98
+ var length = propertiesList . Count ;
99
+
100
+ for ( var i = 0 ; i < length ; i ++ )
97
101
{
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 ] ) ;
120
103
}
121
104
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 )
126
110
{
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 ) ;
130
112
}
113
+
114
+ TrySetMessageTemplateFromState ( ) ;
131
115
}
132
116
133
117
if ( messageTemplate == null )
@@ -163,6 +147,46 @@ LogEvent PrepareWrite<TState>(LogEventLevel level, EventId eventId, TState state
163
147
164
148
var parsedTemplate = messageTemplate != null ? MessageTemplateParser . Parse ( messageTemplate ) : MessageTemplate . Empty ;
165
149
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
+ }
166
190
}
167
191
168
192
static object ? AsLoggableValue < TState > ( TState state , Func < TState , Exception ? , string > ? formatter )
0 commit comments