22using System . Collections . Concurrent ;
33using System . Collections . Generic ;
44using System . Diagnostics ;
5- using System . Linq ;
5+ using System . Reflection ;
6+ using System . Text ;
7+ using System . Text . RegularExpressions ;
68using Spiffy . Monitoring . Config . Formatting ;
79using Spiffy . Monitoring . Config . Naming ;
810
@@ -28,13 +30,43 @@ internal EventContext(string component, string operation, Configuration config)
2830
2931 public EventContext ( ) : this ( null , null )
3032 {
31- var enhancedStackTrace = new EnhancedStackTrace ( new StackTrace ( skipFrames : 1 ) ) ;
33+ var stackTrace = new StackTrace ( skipFrames : 1 ) ;
34+ var frames = stackTrace . GetFrames ( ) ;
35+ if ( frames != null )
36+ {
37+ foreach ( var frame in frames )
38+ {
39+ var method = frame . GetMethod ( ) ;
40+ if ( method ? . DeclaringType != null )
41+ {
42+ var caller = ResolveCaller ( method ) ;
43+ Initialize ( caller . Component , caller . Operation ) ;
44+ break ;
45+ }
46+ }
47+ }
48+ }
49+
50+ // Compiler-generated types (async state machines, lambdas) have a declaring type
51+ // like "Namespace.OuterClass+<MethodName>d__5". We resolve back to the outer class
52+ // and original method name.
53+ static readonly Regex GeneratedTypePattern = new Regex (
54+ @"^<(\w+)>[a-z]__\d+$" , RegexOptions . Compiled ) ;
3255
33- var caller = enhancedStackTrace
34- . First ( f => f . MethodInfo . DeclaringType != null )
35- . MethodInfo ;
56+ static ( string Component , string Operation ) ResolveCaller ( MethodBase method )
57+ {
58+ var declaringType = method . DeclaringType ;
59+
60+ if ( declaringType . DeclaringType != null )
61+ {
62+ var match = GeneratedTypePattern . Match ( declaringType . Name ) ;
63+ if ( match . Success )
64+ {
65+ return ( declaringType . DeclaringType . Name , match . Groups [ 1 ] . Value ) ;
66+ }
67+ }
3668
37- Initialize ( caller . DeclaringType . Name , caller . Name ) ;
69+ return ( declaringType . Name , method . Name ) ;
3870 }
3971
4072 public EventContext ( string component , string operation ) : this ( component , operation , null )
@@ -134,7 +166,10 @@ public void AddValues(params KeyValuePair<string, object>[] values)
134166
135167 public void AddValues ( IEnumerable < KeyValuePair < string , object > > values )
136168 {
137- AddValues ( values . ToArray ( ) ) ;
169+ foreach ( var kvp in values )
170+ {
171+ this [ kvp . Key ] = kvp . Value ;
172+ }
138173 }
139174
140175 public bool Contains ( string key )
@@ -207,7 +242,7 @@ public void Dispose()
207242 if ( ! IsSuppressed )
208243 {
209244 var logActions = _config . LoggingActions ;
210- if ( logActions . Any ( ) )
245+ if ( logActions . Length > 0 )
211246 {
212247 var logEvent = Render ( ) ;
213248 foreach ( var logAction in logActions )
@@ -235,29 +270,31 @@ public void Initialize(string component, string operation)
235270
236271 private LogEvent Render ( )
237272 {
238- IEnumerable < KeyValuePair < string , ( uint Order , object Value ) > > values = _values ;
239- if ( _config . CustomNullValue == null )
273+ // Sort values by insertion order, filter nulls if no custom null value
274+ var sorted = new List < KeyValuePair < string , ( uint Order , object Value ) > > ( _values . Count ) ;
275+ foreach ( var kvp in _values )
240276 {
241- values = values . Where ( kvp => kvp . Value . Value != null ) ;
277+ if ( _config . CustomNullValue != null || kvp . Value . Value != null )
278+ {
279+ sorted . Add ( kvp ) ;
280+ }
242281 }
243- var kvps = values
244- . OrderBy ( x => x . Value . Order )
245- . ToDictionary (
246- kvp => kvp . Key ,
247- kvp => GetValue ( kvp . Value . Value ) ) ;
282+ sorted . Sort ( ( a , b ) => a . Value . Order . CompareTo ( b . Value . Order ) ) ;
248283
249- foreach ( var kvp in GetCountValues ( ) )
284+ var kvps = new Dictionary < string , string > ( sorted . Count + _counts . Count + 8 ) ;
285+ foreach ( var kvp in sorted )
250286 {
251- kvps . Add ( kvp . Key , kvp . Value ) ;
287+ kvps [ kvp . Key ] = GetValue ( kvp . Value . Value ) ;
252288 }
253289
254- foreach ( var kvp in GetTimeValues ( ) )
290+ foreach ( var kvp in _counts )
255291 {
256- kvps . Add ( kvp . Key , kvp . Value ) ;
292+ kvps [ kvp . Key ] = kvp . Value . Value . ToString ( ) ;
257293 }
258294
259- GenerateKeysIfNecessary ( kvps ) ;
295+ GetTimeValues ( kvps ) ;
260296
297+ GenerateKeysIfNecessary ( kvps ) ;
261298 ReplaceKeysThatHaveWhiteSpace ( kvps ) ;
262299 ReplaceKeysThatHaveDots ( kvps ) ;
263300 EncapsulateValuesIfNecessary ( kvps ) ;
@@ -266,7 +303,7 @@ private LogEvent Render()
266303 var formattedTimeElapsed = GetTimeFor ( timeElapsedMs ) ;
267304 this [ FieldName . Get ( Field . TimeElapsed ) ] = formattedTimeElapsed ;
268305 kvps [ FieldName . Get ( Field . TimeElapsed ) ] = formattedTimeElapsed ;
269- PrivateData [ "MetricsKey" ] = $ " { Component } / { Operation } " ;
306+ PrivateData [ "MetricsKey" ] = string . Concat ( Component , "/" , Operation ) ;
270307
271308 return new LogEvent (
272309 Level ,
@@ -299,43 +336,104 @@ void EncapsulateValuesIfNecessary(IDictionary<string, string> keyValuePairs)
299336
300337 private static void ReplaceKeysThatHaveWhiteSpace ( Dictionary < string , string > keyValuePairs )
301338 {
302- foreach ( var kvp in keyValuePairs
303- . Where ( k => k . Key . ContainsWhiteSpace ( ) )
304- . ToList ( ) )
339+ List < KeyValuePair < string , string > > toReplace = null ;
340+ foreach ( var kvp in keyValuePairs )
341+ {
342+ if ( kvp . Key . ContainsWhiteSpace ( ) )
343+ {
344+ ( toReplace ??= new List < KeyValuePair < string , string > > ( ) ) . Add ( kvp ) ;
345+ }
346+ }
347+ if ( toReplace != null )
305348 {
306- keyValuePairs . Remove ( kvp . Key ) ;
307- keyValuePairs [ kvp . Key . RemoveWhiteSpace ( ) ] = kvp . Value ;
349+ foreach ( var kvp in toReplace )
350+ {
351+ keyValuePairs . Remove ( kvp . Key ) ;
352+ keyValuePairs [ kvp . Key . RemoveWhiteSpace ( ) ] = kvp . Value ;
353+ }
308354 }
309355 }
310356
311357 private static void ReplaceKeysThatHaveDots ( Dictionary < string , string > keyValuePairs )
312358 {
313- foreach ( var kvp in keyValuePairs
314- . Where ( k => k . Key . Contains ( "." ) )
315- . ToList ( ) )
359+ List < KeyValuePair < string , string > > toReplace = null ;
360+ foreach ( var kvp in keyValuePairs )
361+ {
362+ if ( kvp . Key . Contains ( "." ) )
363+ {
364+ ( toReplace ??= new List < KeyValuePair < string , string > > ( ) ) . Add ( kvp ) ;
365+ }
366+ }
367+ if ( toReplace != null )
316368 {
317- keyValuePairs . Remove ( kvp . Key ) ;
318- keyValuePairs [ kvp . Key . Replace ( "." , "_" ) ] = kvp . Value ;
369+ foreach ( var kvp in toReplace )
370+ {
371+ keyValuePairs . Remove ( kvp . Key ) ;
372+ keyValuePairs [ kvp . Key . Replace ( "." , "_" ) ] = kvp . Value ;
373+ }
319374 }
320375 }
321376
322377 private void GenerateKeysIfNecessary ( Dictionary < string , string > keyValuePairs )
323378 {
324- foreach ( var kvp in keyValuePairs
325- . Where ( k => string . IsNullOrWhiteSpace ( k . Key ) )
326- . ToList ( ) )
379+ List < KeyValuePair < string , string > > toReplace = null ;
380+ foreach ( var kvp in keyValuePairs )
381+ {
382+ if ( string . IsNullOrWhiteSpace ( kvp . Key ) )
383+ {
384+ ( toReplace ??= new List < KeyValuePair < string , string > > ( ) ) . Add ( kvp ) ;
385+ }
386+ }
387+ if ( toReplace != null )
327388 {
328- keyValuePairs . Remove ( kvp . Key ) ;
329- keyValuePairs [ $ "GeneratedKey({ Guid . NewGuid ( ) } )"] = kvp . Value ;
389+ foreach ( var kvp in toReplace )
390+ {
391+ keyValuePairs . Remove ( kvp . Key ) ;
392+ keyValuePairs [ string . Concat ( "GeneratedKey(" , Guid . NewGuid ( ) . ToString ( ) , ")" ) ] = kvp . Value ;
393+ }
330394 }
331395 }
332396
333397 private string GetKeyValuePairsAsDelimitedString ( Dictionary < string , string > keyValuePairs )
334398 {
335- return string . Join ( " " , keyValuePairs
336- . OrderBy ( pair => pair . Value . Length <= _config . DeprioritizedValueLength ? 0 : 1 )
337- . Select ( kvp =>
338- $ "{ kvp . Key } ={ kvp . Value } ") . ToArray ( ) ) ;
399+ var deprioritizedLength = _config . DeprioritizedValueLength ;
400+
401+ // Partition into short (priority) and long (deprioritized) without LINQ
402+ List < KeyValuePair < string , string > > shortValues = null ;
403+ List < KeyValuePair < string , string > > longValues = null ;
404+ bool hasLong = false ;
405+
406+ foreach ( var kvp in keyValuePairs )
407+ {
408+ if ( kvp . Value . Length <= deprioritizedLength )
409+ {
410+ ( shortValues ??= new List < KeyValuePair < string , string > > ( ) ) . Add ( kvp ) ;
411+ }
412+ else
413+ {
414+ ( longValues ??= new List < KeyValuePair < string , string > > ( ) ) . Add ( kvp ) ;
415+ hasLong = true ;
416+ }
417+ }
418+
419+ var sb = new StringBuilder ( ) ;
420+ if ( shortValues != null )
421+ {
422+ foreach ( var kvp in shortValues )
423+ {
424+ if ( sb . Length > 0 ) sb . Append ( ' ' ) ;
425+ sb . Append ( kvp . Key ) . Append ( '=' ) . Append ( kvp . Value ) ;
426+ }
427+ }
428+ if ( hasLong )
429+ {
430+ foreach ( var kvp in longValues )
431+ {
432+ if ( sb . Length > 0 ) sb . Append ( ' ' ) ;
433+ sb . Append ( kvp . Key ) . Append ( '=' ) . Append ( kvp . Value ) ;
434+ }
435+ }
436+ return sb . ToString ( ) ;
339437 }
340438
341439 string GetValue ( object value )
@@ -378,29 +476,25 @@ private string RenderTimestamp()
378476 return ts ;
379477 }
380478
381- private IDictionary < string , string > GetCountValues ( )
479+ private void GetTimeValues ( Dictionary < string , string > target )
382480 {
383- return _counts . ToDictionary (
384- k => k . Key ,
385- v => v . Value . Value . ToString ( ) ) ;
386- }
481+ var clone = Timers . ShallowClone ( ) ;
482+ if ( clone . Count == 0 ) return ;
387483
388- private IEnumerable < KeyValuePair < string , string > > GetTimeValues ( )
389- {
390- var times = new Dictionary < string , string > ( ) ;
484+ var timeElapsedField = FieldName . Get ( Field . TimeElapsed ) ;
485+ // Sort keys for deterministic output
486+ var keys = new List < string > ( clone . Keys ) ;
487+ keys . Sort ( StringComparer . Ordinal ) ;
391488
392- foreach ( var kvp in Timers
393- . ShallowClone ( )
394- . OrderBy ( x => x . Key ) )
489+ foreach ( var key in keys )
395490 {
396- times [ $ "{ FieldName . Get ( Field . TimeElapsed ) } _{ kvp . Key } "] = GetTimeFor ( kvp . Value . ElapsedMilliseconds ) ;
397- if ( kvp . Value . Count > 1 )
491+ var timer = clone [ key ] ;
492+ target [ string . Concat ( timeElapsedField , "_" , key ) ] = GetTimeFor ( timer . ElapsedMilliseconds ) ;
493+ if ( timer . Count > 1 )
398494 {
399- times [ $ "Count_{ kvp . Key } " ] = kvp . Value . Count . ToString ( ) ;
495+ target [ string . Concat ( "Count_" , key ) ] = timer . Count . ToString ( ) ;
400496 }
401497 }
402-
403- return times ;
404498 }
405499
406500 private static string GetTimeFor ( double milliseconds )
0 commit comments