I am trying to figure out how to log all the properties in a LogEventInfo
object to a JSON-formatted string. Per the issue on github, I tried to do something like this:
<target xsi:type="ColoredConsole" name="coloredConsole">
<layout xsi:type="JsonLayout">
<attribute name="timestamp" layout="${longdate}"/>
<attribute name="level" layout="${level:uppercase=true}"/>
<attribute name="exception" layout="${onexception:${exception:format=tostring}}" />
<attribute name="properties" encode="false">
<layout type="JsonLayout">
<attribute name="properties" layout="${all-event-properties}" />
</layout>
</attribute>
</layout>
</target>
... but unfortunately, my properties contain complex objects (I have two properties with the names of "properties" and "tags", where "properties" is a IDictionary<string, object>
and "tags" is an IList<string>
in the LogEventInfo.Properties
property) that simply do not serialize. I end up with something resembling this:
{ "timestamp": "2017-05-18 08:41:28.7730", "level": "INFO", "properties": { "properties": "properties=System.Collections.Generic.Dictionary`2[System.String,System.Object], tags=System.Collections.Generic.List`1[System.String]" } }
I was expecting (and hoping for) a serialized JSON dictionary that would give me the context of the log message, but clearly that is not what I am getting.
How can I properly serialize the properties in my LogEventInfo
object?
Using NLog 4.5.1 here.
With this config:
<target xsi:type="File" name="jsonFile2" fileName="c:\temp\nlog-json-nested-${shortdate}.log">
<layout type="JsonLayout">
<attribute name="time" layout="${longdate}" />
<attribute name="level" layout="${level}" />
<attribute name="message" layout="${message}" />
<attribute name="eventProperties" encode="false" >
<layout type='JsonLayout' includeAllProperties="true" maxRecursionLimit="20"/>
</attribute>
</layout>
</target>
this code:
var nestedData = new
{
A = "a value",
B = "b value",
Nested = new
{
A = "nested a value",
B = "nested b value",
},
Ints = new Collection<int> { 1, 2, 3},
Dictionary = new Dictionary<string, object>
{
{"nested", new { X= 'x', y = 'y' }},
{ "awesome", "nlog"}
}
};
LogEventInfo eventInfo = new LogEventInfo
{
Level = LogLevel.Info,
Properties = { {"nestedData", nestedData } }
};
logger.Log(eventInfo);
outputs:
{
"time":"2018-04-05 18:08:01.0813",
"level":"INFO",
"eventProperties":{
"nestedData":{
"A":"a value",
"B":"b value",
"Nested":{
"A":"nested a value",
"B":"nested b value"
},
"Ints":[
1,
2,
3
],
"Dictionary":{
"nested":{
"X":"x",
"y":"y"
},
"awesome":"nlog"
}
}
}
}
Actually, it prints the ugly one-line version of this.
See Nested JSON with structured logging on the NLog wiki
Pay attention to the maxRecursionLimit
setting. By default it's 0, meaning "No object reflection", meaning you'll get the ToString()
representation of your event properties.
Well, it looks like there's a bug in NLog, so I kinda made my own renderer. Here's what I did. First, I made an extension method using JSON.NET:
public static string ToJson(this object obj, bool format = false, string dateFormat = null)
{
var settings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
};
if (!String.IsNullOrWhiteSpace(dateFormat))
{
settings.Converters = new List<JsonConverter>
{
new IsoDateTimeConverter {DateTimeFormat = dateFormat}
};
return JsonConvert.SerializeObject(obj, format ? Formatting.Indented : Formatting.None, settings);
}
return JsonConvert.SerializeObject(obj, format ? Formatting.Indented : Formatting.None, settings);
}
...next, I created a LayoutRenderer like so:
[LayoutRenderer("json-event-properties")]
public class JsonEventPropertiesLayoutRenderer : LayoutRenderer
{
/// <summary>
/// Renders the specified environmental information and appends it to the specified <see cref="T:System.Text.StringBuilder" />.
/// </summary>
/// <param name="builder">The <see cref="T:System.Text.StringBuilder" /> to append the rendered data to.</param>
/// <param name="logEvent">Logging event.</param>
protected override void Append(StringBuilder builder, LogEventInfo logEvent) {
if (logEvent.Properties == null || logEvent.Properties.Count == 0)
return;
var serialized = logEvent.Properties.ToJson();
builder.Append(serialized);
}
}
In my application, when starting up, I registered my LayoutRenderer
like so:
LayoutRenderer.Register<JsonEventPropertiesLayoutRenderer>("json-event-properties");
... and finally, I configured the NLog target like this:
<layout xsi:type="JsonLayout">
<attribute name="timestamp" layout="${longdate}"/>
<attribute name="level" layout="${level:uppercase=true}"/>
<attribute name="exception" layout="${onexception:${exception:format=tostring}}" />
<attribute name="message" layout="${message}" />
<attribute name="properties" layout="${json-event-properties}" encode="false"/>
</layout>
Running like this, the JSON properly formatted and I got access to the properties in my LogEventInfo
object.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With