首页 > 解决方案 > 为特定的 NLog 实例设置 ValueFormatter?

问题描述

我有 2 个 NLog 实例,其中一个需要特殊的 ValueFormatter 来序列化参数。ValueFormatter 使用以下代码设置:

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

如您所见,它将应用于所有记录器。我在 NLogger 本身上找不到任何可能采用 ValueFormatter 的属性。

有没有办法将此 ValueFormatter 绑定到其中一个记录器?

编辑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;
    }
}

编辑 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

标签: c#.netnlogformatter

解决方案


NLog JsonLayout 有两个对 LogEvent 属性序列化很重要的选项:

  • 包括所有属性
  • 最大递归限制

默认参数是这样的:

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

但是您也可以像这样激活包含 LogEvent 属性:

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

如果您有这样的自定义对象:

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
    }
}

然后你可以像这样记录对象:

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

默认的 JsonLayout 将只包含默认属性,其中message-attribute 表示Hello Earth

但是 JsonLayoutincludeAllProperties="true"将包含任何额外的 LogEvent 属性。并且将包括World已完全序列化的-propety。

这个想法是人们不应该关心在记录时如何配置 NLog 目标。是 Logging-Rules + Target-Configuration + Layout-Configuration 决定了最终应该如何编写。

如果您确实希望将对象序列化为${message},那么您也可以这样做:

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

如果您不希望 LogEvent-properties 与您的默认属性混合在一起,那么您可以这样做:

<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>

如果您有一个将字段与属性混合的对象,那么您可以告诉 NLog 为该类型执行自定义反射:

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

推荐阅读