title: How to add a custom property to ILogger
body: <p>I want to add a custom property and value to an instance of ILogger obtained from ForContext. The idea being that I can tag all statements with some name, like "Render", or "AI", and then see that group name in the output and also filter on it.</p>
  
  <p>All the examples I see online:</p>
  
  <ul>
  <li><strong>Use ForContext for the class name</strong></li>
  </ul>
  
  <p>I already do that but want this property in addition to the class name</p>
  
  <ul>
  <li><strong>PushProperty, using statements and global Context for per statement properties</strong></li>
  </ul>
  
  <p>I don't want that. I don't want the caller to have to do anything per statement.</p>
  
  <hr>
  
  <p>Here is some code I have thus far for learning purposes:</p>
  
  <pre><code>using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;
  using System.Threading.Tasks;
  
  using Serilog;
  using Serilog.Core;
  using Serilog.Events;
  
  namespace SerilogExtensions {
  
      public static class SerilogForContextExtension {
  
          /// &lt;summary&gt;
          /// Extension method that adds the class name to the source context associated with the logger interface
          /// &lt;/summary&gt;
          /// &lt;typeparam name="T"&gt;&lt;/typeparam&gt;
          /// &lt;param name="that"&gt;&lt;/param&gt;
          /// &lt;returns&gt;&lt;/returns&gt;
          public static ILogger ForMyContext&lt;T&gt;(this ILogger that) =&gt;
              that.ForContext(Constants.SourceContextPropertyName, typeof(T).Name);
  
          public static ILogger ForMyContextWithTag&lt;T&gt;(this ILogger that, string tag) {
              // TODO - What goes here?
              that.ForContext(Constants.SourceContextPropertyName, typeof(T).Name);
              return that;
          }
  
          /// &lt;summary&gt;
          /// Extension method that adds the class name to the source context associated with the logger interface
          /// For use with static classes
          /// &lt;/summary&gt;
          /// &lt;param name="that"&gt;&lt;/param&gt;
          /// &lt;param name="t"&gt;&lt;/param&gt;
          /// &lt;returns&gt;&lt;/returns&gt;
          public static ILogger ForMyContextWithExplicitType(this ILogger that, Type t) =&gt;
              that.ForContext(Constants.SourceContextPropertyName, t.Name);
      }
  
      /// &lt;summary&gt;
      /// POD Class, for serialization to and from file, that contains configurable option we will pass to Serilog
      /// &lt;/summary&gt;
      public class LogConfiguration {
  
          public LogConfiguration() {
              DefaultLevel = LogEventLevel.Verbose;
              Enabled = new List&lt;string&gt;();
              Disabled = new List&lt;string&gt;();
              LogLevelsBySource = new Dictionary&lt;string, LogEventLevel&gt;() { {"SomeClass", 0 }, { "OtherClass", 0 }  };
              OutputTemplate = "[{ThreadId} {Level:u3} {SourceContext} {Tag}] {Message:lj}{NewLine}{Exception}";
          }
  
          /// &lt;summary&gt;
          /// The default logging level
          /// &lt;/summary&gt;
          public LogEventLevel DefaultLevel { get; set; }
  
          /// &lt;summary&gt;
          /// Enable logging by source context class name
          /// &lt;/summary&gt;
          public List&lt;string&gt; Enabled { get; set; }
  
          /// &lt;summary&gt;
          /// Disable logging by source context class name
          /// &lt;/summary&gt;
          public List&lt;string&gt; Disabled { get; set; }
  
          /// &lt;summary&gt;
          /// Configure logging level by source context class name
          /// &lt;/summary&gt;
          public Dictionary&lt;string, LogEventLevel&gt; LogLevelsBySource;
  
          /// &lt;summary&gt;
          /// Determines what each log message will look like. 
          /// Uses Serilog's rules
          /// &lt;/summary&gt;
          public string OutputTemplate { get; set; }
  
          /// &lt;summary&gt;
          /// Overides any previous configuration Serilog is using with one dictated by this class' state
          /// &lt;/summary&gt;
          public void ConfigureSerilog() {
  
              var configuration = new LoggerConfiguration()
                  .MinimumLevel.ControlledBy(new Serilog.Core.LoggingLevelSwitch(DefaultLevel))
                  .Enrich.WithThreadId()
                  .Enrich.FromLogContext()
                  .WriteTo.TextWriter(Console.Out, outputTemplate: OutputTemplate);
  
              var filterExpression = new StringBuilder();
  
              if(Enabled.Count &gt; 0) {
  
                  filterExpression.Append($"@Properties['{Serilog.Core.Constants.SourceContextPropertyName}'] in ['{Enabled[0]}'");
                  for(int index = 1; index &lt; Enabled.Count; ++index) {
                      filterExpression.Append($",'{Enabled[index]}'");
                  }
                  filterExpression.Append("]");
  
                  configuration.Filter.ByIncludingOnly(filterExpression.ToString());
              }
              else if(Disabled.Count &gt; 0) {
  
                  filterExpression.Append($"@Properties['{Serilog.Core.Constants.SourceContextPropertyName}'] in ['{Disabled[0]}'");
                  for (int index = 1; index &lt; Disabled.Count; ++index) {
                      filterExpression.Append($",'{Disabled[index]}'");
                  }
                  filterExpression.Append("]");
  
                  configuration.Filter.ByExcluding(filterExpression.ToString());
              }
  
              foreach(var logLevelForSource in LogLevelsBySource) {
                  configuration.MinimumLevel.Override(logLevelForSource.Key, logLevelForSource.Value);
              }
  
              Log.Logger = configuration.CreateLogger();
          }
      }
  }
  </code></pre>
  
  <hr>
  
  <pre><code>using System;
  using System.IO;
  
  using Newtonsoft.Json;
  using Serilog;
  using SerilogExtensions;
  
  namespace SeriConfigurable {
      public static class MyOptions {
          private static readonly object __lock = new object();
          private static FileSystemWatcher __logLevelWatcher;
  
          /// &lt;summary&gt;
          /// Allows us to configure Serilog from option in a file
          /// &lt;/summary&gt;
          /// &lt;param name="file"&gt;&lt;/param&gt;
          private static void ReadLogLevel(string file) {
  
              LogConfiguration configuration = null;
              if (!File.Exists(file)) {
                  configuration = new LogConfiguration();
                  var jsonAsText = JsonConvert.SerializeObject(configuration);
  
                  using (StreamWriter writer = new StreamWriter(file)) {
                      writer.Write(jsonAsText);
                  }
              }
              else {
                  using (StreamReader reader = new StreamReader(file)) {
                      var jsonAsText = reader.ReadToEnd();
                      configuration = JsonConvert.DeserializeObject&lt;LogConfiguration&gt;(jsonAsText);
                  }
              }
  
              configuration.ConfigureSerilog();
          }
  
          public static void SetOptionsPath(string path) {
              lock (__lock) {
                  string logLevelFile = Path.Combine(path, "logLevel");
  
                  ReadLogLevel(logLevelFile);
  
                  if (__logLevelWatcher != null) {
                      __logLevelWatcher.EnableRaisingEvents = false;
                      __logLevelWatcher.Dispose();
                  }
  
                  __logLevelWatcher = new FileSystemWatcher {
                      Path = Path.GetDirectoryName(logLevelFile),
                      Filter = Path.GetFileName(logLevelFile),
                      NotifyFilter = NotifyFilters.LastWrite
                  };
  
                  __logLevelWatcher.Changed += (sender, e) =&gt; { ReadLogLevel(e.FullPath); };
                  __logLevelWatcher.EnableRaisingEvents = true;
              }
          }
      }
  }
  </code></pre>
  
  <hr>
  
  <pre><code>using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Reflection;
  using System.Text;
  using System.Threading.Tasks;
  
  using Serilog;
  using SerilogExtensions;
  using Serilog.Sinks;
  
  namespace SeriConfigurable {
  
      public class SomeClass {
          private static Serilog.ILogger _log = Serilog.Log.Logger.ForMyContext&lt;SomeClass&gt;();
  
          public SomeClass() {
              _log.Debug("Constructed");
          }
  
          public virtual void Foo() {
              _log.Verbose("Doing Verbose Stuff");
              _log.Information("Doing Information Stuff");
              _log.Debug("Doing Debug Stuff");
              _log.Warning("Doing Warning Stuff");
              _log.Error("Doing Error Stuff");
              _log.Fatal("Doing Fatal Stuff");
  
              var dummyData = new byte[] { 0x01, 0x03, 0xFF, 0x6E, 0xFF };
              StringBuilder hex = new StringBuilder(dummyData.Length * 6);
              foreach (byte oneByte in dummyData)
                  hex.AppendFormat("0x{0:x2}, ", oneByte);
  
              _log.Verbose(string.Format("Received {0} bytes of data: {1}", dummyData.Length, hex.ToString()));
          }
      }
  
      public class OtherClass {
          private static Serilog.ILogger _log = Serilog.Log.Logger.ForMyContext&lt;OtherClass&gt;();
  
          public OtherClass() {
              _log.Debug("Constructed");
          }
  
          public void Foo() {
              _log.Verbose("Doing Verbose Stuff");
              _log.Information("Doing Information Stuff");
              _log.Debug("Doing Debug Stuff");
              _log.Warning("Doing Warning Stuff");
              _log.Error("Doing Error Stuff");
              _log.Fatal("Doing Fatal Stuff");
          }
      }
  
      public class DerivedClass : SomeClass {
          private static Serilog.ILogger _log = Serilog.Log.Logger.ForMyContextWithTag&lt;DerivedClass&gt;("Poop");
  
          public DerivedClass() {
              _log.Debug("Constructed");
          }
  
          public override void Foo() {
  
              _log.Verbose("Doing Verbose Stuff");
              _log.Information("Doing Information Stuff");
              _log.Debug("Doing Debug Stuff");
              _log.Warning("Doing Warning Stuff");
              _log.Error("Doing Error Stuff");
              _log.Fatal("Doing Fatal Stuff");
  
              try {
                  MakeExceptions();
              }
              catch(Exception e) {
                  _log.Error(e, "Bad Things");
              }
          }
  
          public void MakeExceptions() {
              var inner = new BadImageFormatException("You made us look at x86 things");
              var e = new ArgumentException("All of your arguments are bad. You skipped philosophy class", inner);
              throw e;
          }
      }
  
      class Program {
          static void Main(string[] args) {
  
              MyOptions.SetOptionsPath(System.IO.Path.GetDirectoryName(Assembly.GetEntryAssembly().Location));
  
              var someClass = new SomeClass();
              someClass.Foo();
  
              var otherClass = new OtherClass();
              otherClass.Foo();
  
              var derived = new DerivedClass();
              derived.Foo();
          }
      }
  }
  </code></pre>
  
  <p>Expected Behavior:</p>
  
  <pre><code>[1 DBG OtherClass] Constructed
  [1 INF OtherClass] Doing Information Stuff
  [1 DBG OtherClass] Doing Debug Stuff
  [1 WRN OtherClass] Doing Warning Stuff
  [1 ERR OtherClass] Doing Error Stuff
  [1 FTL OtherClass] Doing Fatal Stuff
  [1 DBG DerivedClass Poop] Constructed
  [1 VRB DerivedClass Poop] Doing Verbose Stuff
  [1 INF DerivedClass Poop] Doing Information Stuff
  [1 DBG DerivedClass Poop] Doing Debug Stuff
  [1 WRN DerivedClass Poop] Doing Warning Stuff
  [1 ERR DerivedClass Poop] Doing Error Stuff
  [1 FTL DerivedClass Poop] Doing Fatal Stuff
  [1 ERR DerivedClass Poop] Bad Things
  System.ArgumentException: All of your arguments are bad. You skipped philosophy class ---&gt; System.BadImageFormatException: You made us look at x86 things
     --- End of inner exception stack trace ---
     at SeriConfigurable.DerivedClass.MakeExceptions() in Program.cs:line 82
     at SeriConfigurable.DerivedClass.Foo() in Program.cs:line 72
  </code></pre>