Skip to content

Function Enricher #29

Open
Open
@jmiddour

Description

@jmiddour

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions