How to add a custom property to ILogger
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
add a comment |
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
add a comment |
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
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
c# serilog
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
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
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);
add a comment |
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
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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);
add a comment |
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);
add a comment |
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);
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);
answered Nov 14 '18 at 1:43
Ruben BartelinkRuben Bartelink
43.6k17141202
43.6k17141202
add a comment |
add a comment |
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.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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