Skip to content

NLog properties with Microsoft Extension Logging

Rolf Kristensen edited this page Nov 25, 2017 · 35 revisions

The official extension methods do not provide any obvious way to provide custom NLog properties.

Message Templates

NLog 4.5 supports properties captured from structured logging message templates:

_logger.LogDebug("Logon from {userid}", request.UserId);

These properties can then be extracted using the ${event-properties} renderer.

BeginScope

NLog.Extensions.Logging ver. 1.0 supports async properties captured from the BeginScope input parameter:

using (_logger.BeginScope(new[] { new KeyValuePair<string, object>("userid", request.UserId) }))
{
   _logger.LogDebug("Logon from {0}", request.UserId);
}

These async properties can then be extracted using the ${mdlc} renderer

Log<TState> Simple

Microsoft Extension Logging allows one to log any kind of TState-object, when using the direct Log-method instead of the extension methods.

NLog.Extensions.Logging ver. 1.0 will attempt to cast the TState-object into IEnumerable<KeyValuePair<string, object>>, and if successful then include them as NLog LogEventInfo.Properties.

This can be used to create a custom logevent object that contains both a message and properties.

class MyLogEvent : IEnumerable<KeyValuePair<string, object>>
{
    List<KeyValuePair<string, object>> _properties = new List<KeyValuePair<string, object>>();

    public string Message { get; }
                
    public MyLogEvent(string message)
    {
        Message = message;
    }

    public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
    {
        return _properties.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }

    public MyLogEvent AddProp(string name, object value)
    {
        _properties.Add(new KeyValuePair<string, object>(name, value));
        return this;
    }

    public static Func<MyLogEvent, Exception, string> Formatter { get; } = (l, e) => l.Message;
}

_logger.Log( Microsoft.Extensions.Logging.LogLevel.Debug,
             default(EventId),
             new MyLogEvent($"Logon from {request.UserId}").AddProp("userid", request.UserId),
             (Exception)null,
             MyLogEvent.Formatter );

These properties can then be extracted using the ${event-properties} renderer.

Log<TState> Advanced

Combines structured logging message template with additional properties. It wraps the message formatter from Microsoft Extension Logging, and allows injection of extra properties:

            class MyLogEvent2 : IReadOnlyList<KeyValuePair<string, object>>
            {
                readonly string _format;
                readonly object[] _parameters;
                Microsoft.Extensions.Logging.Internal.FormattedLogValues LogValues => _logValues ?? (_logValues = new Microsoft.Extensions.Logging.Internal.FormattedLogValues(_format, _parameters));
                Microsoft.Extensions.Logging.Internal.FormattedLogValues _logValues;
                List<KeyValuePair<string, object>> _extraProperties;

                public MyLogEvent2(string format, params object[] values)
                {
                    _format = format;
                    _parameters = values;
                }

                public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
                {
                    if (GetMessageParameterCount() == 0)
                    {
                        if (_extraProperties?.Count > 0)
                            return _extraProperties.GetEnumerator();
                        else
                            return Enumerable.Empty<KeyValuePair<string, object>>().GetEnumerator();
                    }
                    else
                    {
                        if (_extraProperties?.Count > 0)
                            return _extraProperties.Concat(LogValues).GetEnumerator();
                        else
                            return LogValues.GetEnumerator();
                    }
                }

                IEnumerator IEnumerable.GetEnumerator()
                {
                    return GetEnumerator();
                }

                private int GetMessageParameterCount()
                {
                    if (LogValues.Count > 1 && !string.IsNullOrEmpty(LogValues[0].Key) && !char.IsDigit(LogValues[0].Key[0]))
                        return LogValues.Count;
                    else
                        return 0;
                }

                public MyLogEvent2 AddProp(string name, object value)
                {
                    var properties = _extraProperties ?? (_extraProperties = new List<KeyValuePair<string, object>>());
                    properties.Add(new KeyValuePair<string, object>(name, value));
                    return this;
                }

                public static Func<MyLogEvent2, Exception, string> Formatter { get; } = (l, e) => l.LogValues.ToString();

                public int Count => GetMessageParameterCount() + (_extraProperties?.Count ?? 0);

                public KeyValuePair<string, object> this[int index]
                {
                    get
                    {
                        int extraCount = _extraProperties?.Count ?? 0;
                        if (index < extraCount)
                        {
                            return _extraProperties[index];
                        }
                        else
                        {
                            return LogValues[index - extraCount];
                        }
                    }
                }
            };

_logger.Log( Microsoft.Extensions.Logging.LogLevel.Debug,
             default(EventId),
             new MyLogEvent2("Logon from {userid}", request.UserId).AddProp("ipaddress", request.IpAddress),
             (Exception)null,
             MyLogEvent2.Formatter );

The properties can then be extracted using the ${event-properties} renderer.

Clone this wiki locally