Set ValueFormatter for specific NLog instance?

半腔热情 提交于 2020-06-01 05:06:43

问题


I have 2 NLog instances where one needs a special ValueFormatter to serialize parameters. The ValueFormatter is set with this code :

NLog.Config.ConfigurationItemFactory.Default.ValueFormatter = new NLogValueFormatter();

So as you can see it will be applied to all loggers. I can´t find any property on the NLogger itself that might take a ValueFormatter.

Is there any way to bind this ValueFormatter just to one of the loggers?

Edit 1:

private CommunicationFormatProvider provider = new CommunicationFormatProvider();
        public void LogCommunication(string message, params object[] args)
        {
            _comLogger.Log(LogLevel.Info, provider,  message, args);
        }

public class CommunicationFormatProvider : IFormatProvider, ICustomFormatter
{
    public string Format(string format, object arg, IFormatProvider formatProvider)
    {
        StringBuilder strBuilder = new StringBuilder();
        strBuilder.Append(format);

        var myTarget = LogManager.Configuration.FindTargetByName("communicationTarget");
        myTarget = ((myTarget as NLog.Targets.Wrappers.WrapperTargetBase)?.WrappedTarget) ?? myTarget;
        var jsonLayout = (myTarget as NLog.Targets.TargetWithLayout)?.Layout as NLog.Layouts.JsonLayout;

        if (jsonLayout?.MaxRecursionLimit > 0)
            strBuilder.Append(JsonConvert.SerializeObject(arg, new JsonSerializerSettings() { MaxDepth = jsonLayout?.MaxRecursionLimit }));

        return strBuilder.ToString();
    }

    object IFormatProvider.GetFormat(Type formatType)
    {
        return (formatType == typeof(ICustomFormatter)) ? this : null;
    }
}

EDIT 2 :

public bool FormatValue(object value, string format, CaptureType captureType, IFormatProvider formatProvider, StringBuilder builder)
{

    if (value.GetType() == typeof(LogData))
        return false;

    builder.Append(format);

    try
    {

        var myTarget = LogManager.Configuration.FindTargetByName("communicationTarget");
        myTarget = ((myTarget as NLog.Targets.Wrappers.WrapperTargetBase)?.WrappedTarget) ?? myTarget;
        var jsonLayout = (myTarget as NLog.Targets.TargetWithLayout)?.Layout as NLog.Layouts.JsonLayout;

        if (jsonLayout?.MaxRecursionLimit > 0)
        {
            var jsonSettings = new JsonSerializerSettings() { MaxDepth = jsonLayout?.MaxRecursionLimit };
            using (var stringWriter = new StringWriter())
            {
                using (var jsonWriter = new JsonTextWriterMaxDepth(stringWriter, jsonSettings))
                    JsonSerializer.Create(jsonSettings).Serialize(jsonWriter, value);
                builder.Append(stringWriter.ToString());
            }
        }
        else
            value = null;
    }
    catch(Exception ex)
    {
        builder.Append($"Failed to serlize {value.GetType()} : {ex.ToString()}");
    }
    return true;
}

JSON Serializer from here : json.net limit maxdepth when serializing


回答1:


NLog JsonLayout has two options that are important for serialization of LogEvent Properties:

  • IncludeAllProperties
  • MaxRecursionLimit

The default parameters is like this:

<layout type="JsonLayout" includeAllProperties="false" maxRecursionLimit="0">
   <attribute name="time" layout="${longdate}" />
   <attribute name="level" layout="${level}"/>
   <attribute name="message" layout="${message}" />
</layout>

But you can also activate inclusion of LogEvent properties like this:

<layout type="JsonLayout" includeAllProperties="true" maxRecursionLimit="10">
   <attribute name="time" layout="${longdate}" />
   <attribute name="level" layout="${level}"/>
   <attribute name="message" layout="${message}" />
</layout>

If you have a custom object like this:

public class Planet
{
    public string Name { get; set; }
    public string PlanetType { get; set; }
    public override string ToString()
    {
        return Name;  // Will be called in normal message-formatting
    }
}

Then you can log the object like this:

logger.Info("Hello {World}", new Planet() { Name = "Earth", PlanetType = "Water Planet" });

The default JsonLayout will just include the default attributes, where message-attribute says Hello Earth.

But the JsonLayout with includeAllProperties="true" will include any additional LogEvent-properties. And will include World-propety that has been serialized fully.

The idea is that one should not care about how NLog Targets are configured when logging. It is the Logging-Rules + Target-Configuration + Layout-Configuration that decides how things should be finally written.

If you really want the object to be serialized into ${message}, then you can also do this:

logger.Info("Hello {@World}", new Planet() { Name = "Earth", PlanetType = "Water Planet" });

If you don't want the LogEvent-properties mixed together with your default properties, then you can do this:

<layout type="JsonLayout" maxRecursionLimit="10">
   <attribute name="time" layout="${longdate}" />
   <attribute name="level" layout="${level}"/>
   <attribute name="message" layout="${message}" />
   <attribute name="properties" encode="false">
      <layout type="JsonLayout" includeAllProperties="true" maxRecursionLimit="10" />
   </attribute>
</layout>

If you have an object that mixes fields with properties, then you can tell NLog to perform custom reflection for that type:

LogManager.Setup().SetupSerialization(s =>
   s.RegisterObjectTransformation<GetEntityViewRequest>(obj => 
       return Newtonsoft.Json.Linq.JToken.FromObject(obj) // Lazy and slow
   )
);



回答2:


You could make sure that the object to be formatted inherits from IFormattable. https://docs.microsoft.com/en-us/dotnet/api/system.iformattable

Then you can implement different formatters, where each Logger can choose their favorite when logging your special object.

Similar to this formatter that provides special formatting of Exception-objects:

https://github.com/NLog/NLog/blob/dev/src/NLog/Internal/ExceptionMessageFormatProvider.cs

Then just make sure to use the IFormatProvider formatProvider-parameter when calling methods on the Logger, when logging your special object.

I guess you could make own custom NLogValueFormatter perform the default formatting. And the Logger that needs special additional formattting can then provide its own custom IFormatProvider.

Update Answer Do you know that you can force NLog to automatically perform JsonSerialization of an object using the message-template:

This will perform the default ToString-operation:

logger.Info("Hello {World}", new { Name = "Earth", Type = "Water Planet" });

This will signal that the object is safe for reflection (Notice the @):

logger.Info("Hello {@World}", new { Name = "Earth", Type = "Water Planet" });

See also: https://github.com/NLog/NLog/wiki/How-to-use-structured-logging



来源:https://stackoverflow.com/questions/61281449/set-valueformatter-for-specific-nlog-instance

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