@@ -17,7 +17,7 @@ public class SerilogLoggerTest
1717 const string Name = "test" ;
1818 const string TestMessage = "This is a test" ;
1919
20- static Tuple < SerilogLogger , SerilogSink > SetUp ( LogLevel logLevel )
20+ static Tuple < SerilogLogger , SerilogSink > SetUp ( LogLevel logLevel , IExternalScopeProvider ? externalScopeProvider = null )
2121 {
2222 var sink = new SerilogSink ( ) ;
2323
@@ -29,6 +29,11 @@ static Tuple<SerilogLogger, SerilogSink> SetUp(LogLevel logLevel)
2929 var provider = new SerilogLoggerProvider ( serilogLogger ) ;
3030 var logger = ( SerilogLogger ) provider . CreateLogger ( Name ) ;
3131
32+ if ( externalScopeProvider is not null )
33+ {
34+ provider . SetScopeProvider ( externalScopeProvider ) ;
35+ }
36+
3237 return new Tuple < SerilogLogger , SerilogSink > ( logger , sink ) ;
3338 }
3439
@@ -397,6 +402,35 @@ public void NamedScopesAreCaptured()
397402 Assert . Equal ( "Inner" , items [ 1 ] ) ;
398403 }
399404
405+ [ Fact ]
406+ public void ExternalScopesAreCaptured ( )
407+ {
408+ var externalScopeProvider = new FakeExternalScopeProvider ( ) ;
409+ var ( logger , sink ) = SetUp ( LogLevel . Trace , externalScopeProvider ) ;
410+
411+ externalScopeProvider . Push ( new Dictionary < string , int > ( )
412+ {
413+ { "FirstKey" , 1 } ,
414+ { "SecondKey" , 2 }
415+ } ) ;
416+
417+ var scopeObject = new { ObjectKey = "Some value" } ;
418+ externalScopeProvider . Push ( scopeObject ) ;
419+
420+ logger . Log ( LogLevel . Information , 0 , TestMessage , null ! , null ! ) ;
421+
422+ Assert . Single ( sink . Writes ) ;
423+ Assert . True ( sink . Writes [ 0 ] . Properties . TryGetValue ( SerilogLoggerProvider . ScopePropertyName , out var scopeValue ) ) ;
424+ var sequence = Assert . IsType < SequenceValue > ( scopeValue ) ;
425+
426+ var objectScope = ( ScalarValue ) sequence . Elements . Single ( e => e is ScalarValue ) ;
427+ Assert . Equal ( scopeObject . ToString ( ) , ( string ? ) objectScope . Value ) ;
428+
429+ var dictionaryScope = ( DictionaryValue ) sequence . Elements . Single ( e => e is DictionaryValue ) ;
430+ Assert . Equal ( 1 , ( ( ScalarValue ) dictionaryScope . Elements . Single ( pair => pair . Key . Value ! . Equals ( "FirstKey" ) ) . Value ) . Value ) ;
431+ Assert . Equal ( 2 , ( ( ScalarValue ) dictionaryScope . Elements . Single ( pair => pair . Key . Value ! . Equals ( "SecondKey" ) ) . Value ) . Value ) ;
432+ }
433+
400434 class FoodScope : IEnumerable < KeyValuePair < string , object > >
401435 {
402436 readonly string _name ;
@@ -446,6 +480,43 @@ class Person
446480 public string ? LastName { get ; set ; }
447481 }
448482
483+ class FakeExternalScopeProvider : IExternalScopeProvider
484+ {
485+ private readonly List < Scope > _scopes = new List < Scope > ( ) ;
486+
487+ public void ForEachScope < TState > ( Action < object ? , TState > callback , TState state )
488+ {
489+ foreach ( var scope in _scopes )
490+ {
491+ if ( scope . IsDisposed ) continue ;
492+ callback ( scope . Value , state ) ;
493+ }
494+ }
495+
496+ public IDisposable Push ( object ? state )
497+ {
498+ var scope = new Scope ( state ) ;
499+ _scopes . Add ( scope ) ;
500+ return scope ;
501+ }
502+
503+ private class Scope : IDisposable
504+ {
505+ public bool IsDisposed { get ; set ; } = false ;
506+ public object ? Value { get ; set ; }
507+
508+ public Scope ( object ? value )
509+ {
510+ Value = value ;
511+ }
512+
513+ public void Dispose ( )
514+ {
515+ IsDisposed = true ;
516+ }
517+ }
518+ }
519+
449520 [ Theory ]
450521 [ InlineData ( 1 ) ]
451522 [ InlineData ( 10 ) ]
0 commit comments