Description
First off, apologies if this is in the wrong location as it didn't exactly fit any of the repos
I had a need where I wanted to enrich a log event with the current value of a property at the time it was logged. Specifically, we have desktop applications that are able to change Environment dynamically, so I wanted a way to reflect that in log messages -- and route log events to the correct sinks. I know ForContext<> already does this to some degree, but I didn't want to have to worry about popping and re-pushing when the property value changes.
So, took the existing property enricher and changed it to enrich with the result of the function. I am sharing below to see if anyone else might be interested or will know if it runs counter to a specific design choice. Also, if anyone has a better way of doing it, I'd be interested in that as well.
The code:
public class FunctionEnricher : ILogEventEnricher
{
readonly string _name;
readonly Func<object> _value;
readonly bool _destructureObjects;
/// <summary>
/// Create a new Function enricher.
/// </summary>
/// <param name="name">The name of the property.</param>
/// <param name="value">Function to evaluate.</param>
/// <returns>A handle to later remove the property from the context.</returns>
/// <param name="destructureObjects">If true, and the value is a non-primitive, non-array type,
/// then the value will be converted to a structure; otherwise, unknown types will
/// be converted to scalars, which are generally stored as strings.</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public FunctionEnricher(string name, Func<object> value, bool destructureObjects = false)
{
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Property name must not be null or empty.", nameof(name));
_name = name;
_value = value;
_destructureObjects = destructureObjects;
}
/// <summary>
/// Enrich the log event.
/// </summary>
/// <param name="logEvent">The log event to enrich.</param>
/// <param name="propertyFactory">Factory for creating new properties to add to the event.</param>
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
if (propertyFactory == null) throw new ArgumentNullException(nameof(propertyFactory));
var property = propertyFactory.CreateProperty(_name, _value.Invoke(), _destructureObjects);
logEvent.AddPropertyIfAbsent(property);
}
}
And an example of it's usage:
static void Main(string[] args)
{
string env = "test";
var log = GetLoggerConfiguration(()=>env)
.WriteTo.Logger(lc=> lc.Filter.ByExcluding(Matching.WithProperty("Environment","prod"))
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Environment} {Message:lj}{NewLine}"))
.WriteTo.Logger(lc => lc.Filter.ByIncludingOnly(Matching.WithProperty("Environment", "prod"))
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] prodonly {Message:lj}{NewLine}"))
.WriteTo.Console()
.CreateLogger();
log.Error("This is a test log message");
env = "prod";
log.Error("This is another log message");
Console.ReadLine();
}
public static LoggerConfiguration GetLoggerConfiguration(Func<object> Environment)
{
var _loggerConfig = new LoggerConfiguration()
.Enrich.FromLogContext()
.Enrich.WithProperty("ApplicationInstanceId", ApplicationInstanceId)
.Enrich.With(new FunctionValueEnricher("Environment",()=>Environment));
return _loggerConfig;
}
And the output:
[15:22:23 ERR] test This is a test log message
[15:22:23 ERR] This is a test log message
[15:22:24 ERR] prodonly This is another log message
[15:22:24 ERR] This is another log message