Layout NLog properties as JSON?

五迷三道 提交于 2020-05-27 05:49:45

问题


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?


回答1:


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.




回答2:


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.



来源:https://stackoverflow.com/questions/44048187/layout-nlog-properties-as-json

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!