-
Notifications
You must be signed in to change notification settings - Fork 150
NLog properties with Microsoft Extension Logging
The official extension methods in Microsoft Extension Logging does not provide any obvious way to provide custom NLog properties.
But there are still different options available:
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.
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
Notice with BeginScope
then ${ndlctiming} can be used to extract the timespan from scope was started and logevent was made. This can be used for showing current duration of the active request.
Microsoft Extension Logging can 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 WithProperty(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}").WithProperty("userid", request.UserId),
(Exception)null,
MyLogEvent.Formatter );
These properties can then be extracted using the ${event-properties} renderer.
Combines structured logging message template with additional properties. It wraps the message formatter from Microsoft Extension Logging, and allows injection of extra properties:
public class MyLogEvent2 : Microsoft.Extensions.Logging.ILogger, IReadOnlyList<KeyValuePair<string, object>>
{
readonly string _format;
readonly object[] _parameters;
IReadOnlyList<KeyValuePair<string, object>> _logValues;
List<KeyValuePair<string, object>> _extraProperties;
public MyLogEvent2(string format, params object[] values)
{
_format = format;
_parameters = values;
}
public MyLogEvent2 WithProperty(string name, object value)
{
var properties = _extraProperties ?? (_extraProperties = new List<KeyValuePair<string, object>>());
properties.Add(new KeyValuePair<string, object>(name, value));
return this;
}
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
if (MessagePropertyCount == 0)
{
if (ExtraPropertyCount > 0)
return _extraProperties.GetEnumerator();
else
return System.Linq.Enumerable.Empty<KeyValuePair<string, object>>().GetEnumerator();
}
else
{
if (ExtraPropertyCount > 0)
return System.Linq.Enumerable.Concat(_extraProperties, LogValues).GetEnumerator();
else
return LogValues.GetEnumerator();
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public KeyValuePair<string, object> this[int index]
{
get
{
int extraCount = ExtraPropertyCount;
if (index < extraCount)
{
return _extraProperties[index];
}
else
{
return LogValues[index - extraCount];
}
}
}
public int Count => MessagePropertyCount + ExtraPropertyCount;
public override string ToString() => LogValues.ToString();
private IReadOnlyList<KeyValuePair<string, object>> LogValues
{
get
{
if (_logValues == null)
Microsoft.Extensions.Logging.LoggerExtensions.LogDebug(this, _format, _parameters);
return _logValues;
}
}
private int ExtraPropertyCount => _extraProperties?.Count ?? 0;
private int MessagePropertyCount
{
get
{
if (LogValues.Count > 1 && !string.IsNullOrEmpty(LogValues[0].Key) && !char.IsDigit(LogValues[0].Key[0]))
return LogValues.Count;
else
return 0;
}
}
void Microsoft.Extensions.Logging.ILogger.Log<TState>(Microsoft.Extensions.Logging.LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
_logValues = state as IReadOnlyList<KeyValuePair<string, object>> ?? Array.Empty<KeyValuePair<string, object>>();
}
bool Microsoft.Extensions.Logging.ILogger.IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel)
{
return true;
}
IDisposable Microsoft.Extensions.Logging.ILogger.BeginScope<TState>(TState state)
{
throw new NotSupportedException();
}
public static Func<MyLogEvent2, Exception, string> Formatter { get; } = (l, e) => l.LogValues.ToString();
}
_logger.Log( Microsoft.Extensions.Logging.LogLevel.Debug,
default(EventId),
new MyLogEvent2("Logon from {userid}", request.UserId).WithProperty("ipaddress", request.IpAddress),
(Exception)null,
MyLogEvent2.Formatter );
The properties can then be extracted using the ${event-properties} renderer.