@@ -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