How to add a custom property to ILogger










3















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.



All the examples I see online:



  • Use ForContext for the class name

I already do that but want this property in addition to the class name



  • PushProperty, using statements and global Context for per statement properties

I don't want that. I don't want the caller to have to do anything per statement.




Here is some code I have thus far for learning purposes:



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

/// <summary>
/// Extension method that adds the class name to the source context associated with the logger interface
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="that"></param>
/// <returns></returns>
public static ILogger ForMyContext<T>(this ILogger that) =>
that.ForContext(Constants.SourceContextPropertyName, typeof(T).Name);

public static ILogger ForMyContextWithTag<T>(this ILogger that, string tag)
// TODO - What goes here?
that.ForContext(Constants.SourceContextPropertyName, typeof(T).Name);
return that;


/// <summary>
/// Extension method that adds the class name to the source context associated with the logger interface
/// For use with static classes
/// </summary>
/// <param name="that"></param>
/// <param name="t"></param>
/// <returns></returns>
public static ILogger ForMyContextWithExplicitType(this ILogger that, Type t) =>
that.ForContext(Constants.SourceContextPropertyName, t.Name);


/// <summary>
/// POD Class, for serialization to and from file, that contains configurable option we will pass to Serilog
/// </summary>
public class LogConfiguration

public LogConfiguration()
DefaultLevel = LogEventLevel.Verbose;
Enabled = new List<string>();
Disabled = new List<string>();
LogLevelsBySource = new Dictionary<string, LogEventLevel>() "SomeClass", 0 , "OtherClass", 0 ;
OutputTemplate = "[ThreadId Level:u3 SourceContext Tag] Message:ljNewLineException";


/// <summary>
/// The default logging level
/// </summary>
public LogEventLevel DefaultLevel get; set;

/// <summary>
/// Enable logging by source context class name
/// </summary>
public List<string> Enabled get; set;

/// <summary>
/// Disable logging by source context class name
/// </summary>
public List<string> Disabled get; set;

/// <summary>
/// Configure logging level by source context class name
/// </summary>
public Dictionary<string, LogEventLevel> LogLevelsBySource;

/// <summary>
/// Determines what each log message will look like.
/// Uses Serilog's rules
/// </summary>
public string OutputTemplate get; set;

/// <summary>
/// Overides any previous configuration Serilog is using with one dictated by this class' state
/// </summary>
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 > 0)

filterExpression.Append($"@Properties['Serilog.Core.Constants.SourceContextPropertyName'] in ['Enabled[0]'");
for(int index = 1; index < Enabled.Count; ++index)
filterExpression.Append($",'Enabled[index]'");

filterExpression.Append("]");

configuration.Filter.ByIncludingOnly(filterExpression.ToString());

else if(Disabled.Count > 0)

filterExpression.Append($"@Properties['Serilog.Core.Constants.SourceContextPropertyName'] in ['Disabled[0]'");
for (int index = 1; index < 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();






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;

/// <summary>
/// Allows us to configure Serilog from option in a file
/// </summary>
/// <param name="file"></param>
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<LogConfiguration>(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) => ReadLogLevel(e.FullPath); ;
__logLevelWatcher.EnableRaisingEvents = true;







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<SomeClass>();

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("0x0: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<OtherClass>();

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<DerivedClass>("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();





Expected Behavior:



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









share|improve this question




























    3















    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.



    All the examples I see online:



    • Use ForContext for the class name

    I already do that but want this property in addition to the class name



    • PushProperty, using statements and global Context for per statement properties

    I don't want that. I don't want the caller to have to do anything per statement.




    Here is some code I have thus far for learning purposes:



    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

    /// <summary>
    /// Extension method that adds the class name to the source context associated with the logger interface
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="that"></param>
    /// <returns></returns>
    public static ILogger ForMyContext<T>(this ILogger that) =>
    that.ForContext(Constants.SourceContextPropertyName, typeof(T).Name);

    public static ILogger ForMyContextWithTag<T>(this ILogger that, string tag)
    // TODO - What goes here?
    that.ForContext(Constants.SourceContextPropertyName, typeof(T).Name);
    return that;


    /// <summary>
    /// Extension method that adds the class name to the source context associated with the logger interface
    /// For use with static classes
    /// </summary>
    /// <param name="that"></param>
    /// <param name="t"></param>
    /// <returns></returns>
    public static ILogger ForMyContextWithExplicitType(this ILogger that, Type t) =>
    that.ForContext(Constants.SourceContextPropertyName, t.Name);


    /// <summary>
    /// POD Class, for serialization to and from file, that contains configurable option we will pass to Serilog
    /// </summary>
    public class LogConfiguration

    public LogConfiguration()
    DefaultLevel = LogEventLevel.Verbose;
    Enabled = new List<string>();
    Disabled = new List<string>();
    LogLevelsBySource = new Dictionary<string, LogEventLevel>() "SomeClass", 0 , "OtherClass", 0 ;
    OutputTemplate = "[ThreadId Level:u3 SourceContext Tag] Message:ljNewLineException";


    /// <summary>
    /// The default logging level
    /// </summary>
    public LogEventLevel DefaultLevel get; set;

    /// <summary>
    /// Enable logging by source context class name
    /// </summary>
    public List<string> Enabled get; set;

    /// <summary>
    /// Disable logging by source context class name
    /// </summary>
    public List<string> Disabled get; set;

    /// <summary>
    /// Configure logging level by source context class name
    /// </summary>
    public Dictionary<string, LogEventLevel> LogLevelsBySource;

    /// <summary>
    /// Determines what each log message will look like.
    /// Uses Serilog's rules
    /// </summary>
    public string OutputTemplate get; set;

    /// <summary>
    /// Overides any previous configuration Serilog is using with one dictated by this class' state
    /// </summary>
    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 > 0)

    filterExpression.Append($"@Properties['Serilog.Core.Constants.SourceContextPropertyName'] in ['Enabled[0]'");
    for(int index = 1; index < Enabled.Count; ++index)
    filterExpression.Append($",'Enabled[index]'");

    filterExpression.Append("]");

    configuration.Filter.ByIncludingOnly(filterExpression.ToString());

    else if(Disabled.Count > 0)

    filterExpression.Append($"@Properties['Serilog.Core.Constants.SourceContextPropertyName'] in ['Disabled[0]'");
    for (int index = 1; index < 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();






    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;

    /// <summary>
    /// Allows us to configure Serilog from option in a file
    /// </summary>
    /// <param name="file"></param>
    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<LogConfiguration>(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) => ReadLogLevel(e.FullPath); ;
    __logLevelWatcher.EnableRaisingEvents = true;







    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<SomeClass>();

    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("0x0: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<OtherClass>();

    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<DerivedClass>("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();





    Expected Behavior:



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









    share|improve this question


























      3












      3








      3








      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.



      All the examples I see online:



      • Use ForContext for the class name

      I already do that but want this property in addition to the class name



      • PushProperty, using statements and global Context for per statement properties

      I don't want that. I don't want the caller to have to do anything per statement.




      Here is some code I have thus far for learning purposes:



      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

      /// <summary>
      /// Extension method that adds the class name to the source context associated with the logger interface
      /// </summary>
      /// <typeparam name="T"></typeparam>
      /// <param name="that"></param>
      /// <returns></returns>
      public static ILogger ForMyContext<T>(this ILogger that) =>
      that.ForContext(Constants.SourceContextPropertyName, typeof(T).Name);

      public static ILogger ForMyContextWithTag<T>(this ILogger that, string tag)
      // TODO - What goes here?
      that.ForContext(Constants.SourceContextPropertyName, typeof(T).Name);
      return that;


      /// <summary>
      /// Extension method that adds the class name to the source context associated with the logger interface
      /// For use with static classes
      /// </summary>
      /// <param name="that"></param>
      /// <param name="t"></param>
      /// <returns></returns>
      public static ILogger ForMyContextWithExplicitType(this ILogger that, Type t) =>
      that.ForContext(Constants.SourceContextPropertyName, t.Name);


      /// <summary>
      /// POD Class, for serialization to and from file, that contains configurable option we will pass to Serilog
      /// </summary>
      public class LogConfiguration

      public LogConfiguration()
      DefaultLevel = LogEventLevel.Verbose;
      Enabled = new List<string>();
      Disabled = new List<string>();
      LogLevelsBySource = new Dictionary<string, LogEventLevel>() "SomeClass", 0 , "OtherClass", 0 ;
      OutputTemplate = "[ThreadId Level:u3 SourceContext Tag] Message:ljNewLineException";


      /// <summary>
      /// The default logging level
      /// </summary>
      public LogEventLevel DefaultLevel get; set;

      /// <summary>
      /// Enable logging by source context class name
      /// </summary>
      public List<string> Enabled get; set;

      /// <summary>
      /// Disable logging by source context class name
      /// </summary>
      public List<string> Disabled get; set;

      /// <summary>
      /// Configure logging level by source context class name
      /// </summary>
      public Dictionary<string, LogEventLevel> LogLevelsBySource;

      /// <summary>
      /// Determines what each log message will look like.
      /// Uses Serilog's rules
      /// </summary>
      public string OutputTemplate get; set;

      /// <summary>
      /// Overides any previous configuration Serilog is using with one dictated by this class' state
      /// </summary>
      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 > 0)

      filterExpression.Append($"@Properties['Serilog.Core.Constants.SourceContextPropertyName'] in ['Enabled[0]'");
      for(int index = 1; index < Enabled.Count; ++index)
      filterExpression.Append($",'Enabled[index]'");

      filterExpression.Append("]");

      configuration.Filter.ByIncludingOnly(filterExpression.ToString());

      else if(Disabled.Count > 0)

      filterExpression.Append($"@Properties['Serilog.Core.Constants.SourceContextPropertyName'] in ['Disabled[0]'");
      for (int index = 1; index < 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();






      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;

      /// <summary>
      /// Allows us to configure Serilog from option in a file
      /// </summary>
      /// <param name="file"></param>
      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<LogConfiguration>(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) => ReadLogLevel(e.FullPath); ;
      __logLevelWatcher.EnableRaisingEvents = true;







      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<SomeClass>();

      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("0x0: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<OtherClass>();

      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<DerivedClass>("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();





      Expected Behavior:



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









      share|improve this question
















      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.



      All the examples I see online:



      • Use ForContext for the class name

      I already do that but want this property in addition to the class name



      • PushProperty, using statements and global Context for per statement properties

      I don't want that. I don't want the caller to have to do anything per statement.




      Here is some code I have thus far for learning purposes:



      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

      /// <summary>
      /// Extension method that adds the class name to the source context associated with the logger interface
      /// </summary>
      /// <typeparam name="T"></typeparam>
      /// <param name="that"></param>
      /// <returns></returns>
      public static ILogger ForMyContext<T>(this ILogger that) =>
      that.ForContext(Constants.SourceContextPropertyName, typeof(T).Name);

      public static ILogger ForMyContextWithTag<T>(this ILogger that, string tag)
      // TODO - What goes here?
      that.ForContext(Constants.SourceContextPropertyName, typeof(T).Name);
      return that;


      /// <summary>
      /// Extension method that adds the class name to the source context associated with the logger interface
      /// For use with static classes
      /// </summary>
      /// <param name="that"></param>
      /// <param name="t"></param>
      /// <returns></returns>
      public static ILogger ForMyContextWithExplicitType(this ILogger that, Type t) =>
      that.ForContext(Constants.SourceContextPropertyName, t.Name);


      /// <summary>
      /// POD Class, for serialization to and from file, that contains configurable option we will pass to Serilog
      /// </summary>
      public class LogConfiguration

      public LogConfiguration()
      DefaultLevel = LogEventLevel.Verbose;
      Enabled = new List<string>();
      Disabled = new List<string>();
      LogLevelsBySource = new Dictionary<string, LogEventLevel>() "SomeClass", 0 , "OtherClass", 0 ;
      OutputTemplate = "[ThreadId Level:u3 SourceContext Tag] Message:ljNewLineException";


      /// <summary>
      /// The default logging level
      /// </summary>
      public LogEventLevel DefaultLevel get; set;

      /// <summary>
      /// Enable logging by source context class name
      /// </summary>
      public List<string> Enabled get; set;

      /// <summary>
      /// Disable logging by source context class name
      /// </summary>
      public List<string> Disabled get; set;

      /// <summary>
      /// Configure logging level by source context class name
      /// </summary>
      public Dictionary<string, LogEventLevel> LogLevelsBySource;

      /// <summary>
      /// Determines what each log message will look like.
      /// Uses Serilog's rules
      /// </summary>
      public string OutputTemplate get; set;

      /// <summary>
      /// Overides any previous configuration Serilog is using with one dictated by this class' state
      /// </summary>
      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 > 0)

      filterExpression.Append($"@Properties['Serilog.Core.Constants.SourceContextPropertyName'] in ['Enabled[0]'");
      for(int index = 1; index < Enabled.Count; ++index)
      filterExpression.Append($",'Enabled[index]'");

      filterExpression.Append("]");

      configuration.Filter.ByIncludingOnly(filterExpression.ToString());

      else if(Disabled.Count > 0)

      filterExpression.Append($"@Properties['Serilog.Core.Constants.SourceContextPropertyName'] in ['Disabled[0]'");
      for (int index = 1; index < 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();






      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;

      /// <summary>
      /// Allows us to configure Serilog from option in a file
      /// </summary>
      /// <param name="file"></param>
      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<LogConfiguration>(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) => ReadLogLevel(e.FullPath); ;
      __logLevelWatcher.EnableRaisingEvents = true;







      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<SomeClass>();

      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("0x0: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<OtherClass>();

      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<DerivedClass>("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();





      Expected Behavior:



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






      c# serilog






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Nov 14 '18 at 1:15









      Ruben Bartelink

      43.6k17141202




      43.6k17141202










      asked Nov 13 '18 at 22:21









      Christopher PiszChristopher Pisz

      1,484521




      1,484521






















          1 Answer
          1






          active

          oldest

          votes


















          3














          public static ILogger ForMyContextWithTag<T>(this ILogger that, string tag) 
          // TODO - What goes here?
          that.ForContext(Constants.SourceContextPropertyName, typeof(T).Name);
          return that;



          Needs to become:



          public static ILogger ForMyContextWithTag<T>(this ILogger that, string tag) 
          return that
          .ForContext(Constants.SourceContextPropertyName, typeof(T).Name)
          .ForContext("Tag", tag);




          Which is



          public static ILogger ForMyContextWithTag<T>(this ILogger that, string tag) =>
          that.ForContext(Constants.SourceContextPropertyName, typeof(T).Name)
          .ForContext("Tag", tag);





          share|improve this answer






















            Your Answer






            StackExchange.ifUsing("editor", function ()
            StackExchange.using("externalEditor", function ()
            StackExchange.using("snippets", function ()
            StackExchange.snippets.init();
            );
            );
            , "code-snippets");

            StackExchange.ready(function()
            var channelOptions =
            tags: "".split(" "),
            id: "1"
            ;
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function()
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled)
            StackExchange.using("snippets", function()
            createEditor();
            );

            else
            createEditor();

            );

            function createEditor()
            StackExchange.prepareEditor(
            heartbeatType: 'answer',
            autoActivateHeartbeat: false,
            convertImagesToLinks: true,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: 10,
            bindNavPrevention: true,
            postfix: "",
            imageUploader:
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            ,
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            );



            );













            draft saved

            draft discarded


















            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53290408%2fhow-to-add-a-custom-property-to-ilogger%23new-answer', 'question_page');

            );

            Post as a guest















            Required, but never shown

























            1 Answer
            1






            active

            oldest

            votes








            1 Answer
            1






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes









            3














            public static ILogger ForMyContextWithTag<T>(this ILogger that, string tag) 
            // TODO - What goes here?
            that.ForContext(Constants.SourceContextPropertyName, typeof(T).Name);
            return that;



            Needs to become:



            public static ILogger ForMyContextWithTag<T>(this ILogger that, string tag) 
            return that
            .ForContext(Constants.SourceContextPropertyName, typeof(T).Name)
            .ForContext("Tag", tag);




            Which is



            public static ILogger ForMyContextWithTag<T>(this ILogger that, string tag) =>
            that.ForContext(Constants.SourceContextPropertyName, typeof(T).Name)
            .ForContext("Tag", tag);





            share|improve this answer



























              3














              public static ILogger ForMyContextWithTag<T>(this ILogger that, string tag) 
              // TODO - What goes here?
              that.ForContext(Constants.SourceContextPropertyName, typeof(T).Name);
              return that;



              Needs to become:



              public static ILogger ForMyContextWithTag<T>(this ILogger that, string tag) 
              return that
              .ForContext(Constants.SourceContextPropertyName, typeof(T).Name)
              .ForContext("Tag", tag);




              Which is



              public static ILogger ForMyContextWithTag<T>(this ILogger that, string tag) =>
              that.ForContext(Constants.SourceContextPropertyName, typeof(T).Name)
              .ForContext("Tag", tag);





              share|improve this answer

























                3












                3








                3







                public static ILogger ForMyContextWithTag<T>(this ILogger that, string tag) 
                // TODO - What goes here?
                that.ForContext(Constants.SourceContextPropertyName, typeof(T).Name);
                return that;



                Needs to become:



                public static ILogger ForMyContextWithTag<T>(this ILogger that, string tag) 
                return that
                .ForContext(Constants.SourceContextPropertyName, typeof(T).Name)
                .ForContext("Tag", tag);




                Which is



                public static ILogger ForMyContextWithTag<T>(this ILogger that, string tag) =>
                that.ForContext(Constants.SourceContextPropertyName, typeof(T).Name)
                .ForContext("Tag", tag);





                share|improve this answer













                public static ILogger ForMyContextWithTag<T>(this ILogger that, string tag) 
                // TODO - What goes here?
                that.ForContext(Constants.SourceContextPropertyName, typeof(T).Name);
                return that;



                Needs to become:



                public static ILogger ForMyContextWithTag<T>(this ILogger that, string tag) 
                return that
                .ForContext(Constants.SourceContextPropertyName, typeof(T).Name)
                .ForContext("Tag", tag);




                Which is



                public static ILogger ForMyContextWithTag<T>(this ILogger that, string tag) =>
                that.ForContext(Constants.SourceContextPropertyName, typeof(T).Name)
                .ForContext("Tag", tag);






                share|improve this answer












                share|improve this answer



                share|improve this answer










                answered Nov 14 '18 at 1:43









                Ruben BartelinkRuben Bartelink

                43.6k17141202




                43.6k17141202



























                    draft saved

                    draft discarded
















































                    Thanks for contributing an answer to Stack Overflow!


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid


                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.

                    To learn more, see our tips on writing great answers.




                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function ()
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53290408%2fhow-to-add-a-custom-property-to-ilogger%23new-answer', 'question_page');

                    );

                    Post as a guest















                    Required, but never shown





















































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown

































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown







                    這個網誌中的熱門文章

                    Barbados

                    How to read a connectionString WITH PROVIDER in .NET Core?

                    Node.js Script on GitHub Pages or Amazon S3