【问题标题】:Set ValueFormatter for specific NLog instance?为特定的 NLog 实例设置 ValueFormatter?
【发布时间】:2020-04-17 22:02:17
【问题描述】:

我有 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 序列化器:json.net limit maxdepth when serializing

【问题讨论】:

  • 顺便说一句。 JsonSerializerSettings.MaxDepth 在这里无效。它仅在“阅读”时使用,而不是在像您在这里所做的“写作”时使用。 (所以不需要在 NLog-config 中进行任何查找)

标签: c# .net nlog formatter


【解决方案1】:

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

  • 包括所有属性
  • MaxRecursionLimit

默认参数是这样的:

<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

但是带有includeAllProperties="true" 的 JsonLayout 将包含任何额外的 LogEvent 属性。并将包含已完全序列化的World-propety。

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

如果你真的希望对象被序列化成${message},那么你也可以这样做:

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

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

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

【讨论】:

  • 谢谢,你在另一篇文章“NLog 仅序列化对象属性默认情况下(当有适当的 getter 时)。它忽略对象字段。”,这是我需要做手册的原因我的对象的序列化。我上面帖子中提供的代码可以按我的需要工作,它会输出正确的数据,但我知道我的方法不是有意的。那么如何使用 config 中的 maxRecursionLimit 为 1 个特定 Logger 自定义序列化?
  • 您确保特殊的 Logger 命中了一个配置为 maxRecursionLimit= 1 的目标,并且该目标的布局将进行反射。您可以像这样为自己的类型(包含字段)自定义反射:github.com/NLog/NLog/wiki/…
  • @Banshee 所以你的问题不是“如何使用 maxRecursionLimit 为 1 个特定 Logger 的配置自定义序列化?”但是“如何自定义带有字段的对象的序列化?”
  • @Banshee 但是是的,这个答案几乎是我在重复 stackoverflow.com/a/61104478/193178 。你有什么理由不想打电话给 NLog RegisterObjectTransformation
  • 我们收集了大量不同的复杂对象,其中包含字段、默认值和属性,GetEntityViewRequest 只是众多对象之一。所有这些都需要以一种通用的方式进行序列化,我们可以在其中设置运行时的深度。这也需要仅在处理通信的 NLogger 上设置,而不是在我们的其他 NLogger 上设置。我可以在我的第一篇文章中进行 selization 以尊重配置文件的深度,但听起来这是一个糟糕的方法。那么在不操纵数据模型的情况下,哪种方式最适合我们的数据模型呢?请注意,我们所有的类都是带有 DataMembers 的 DataContracts。
【解决方案2】:

您可以确保要格式化的对象继承自IFormattablehttps://docs.microsoft.com/en-us/dotnet/api/system.iformattable

然后您可以实现不同的格式化程序,每个 Logger 在记录您的特殊对象时可以选择自己喜欢的格式。

类似于这个提供异常对象特殊格式的格式化程序:

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

然后,在记录您的特殊对象时,请确保在调用 Logger 上的方法时使用 IFormatProvider formatProvider-参数。

我猜你可以让自己的自定义NLogValueFormatter 执行默认格式。然后需要特殊附加格式的 Logger 可以提供自己的自定义IFormatProvider

更新答案你知道可以强制 NLog 使用消息模板自动执行对象的 JsonSerialization:

这将执行默认的 ToString 操作:

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

这将表明对象可以安全地进行反射(注意@):

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

另见:https://github.com/NLog/NLog/wiki/How-to-use-structured-logging

【讨论】:

  • 谢谢,我创建了一个在调用 ILogger.Log(LogLevel, IFormatProvider, string object[]) 时提供的 IFormatProvider 实现,但从未触发 Format?请参阅编辑。
  • 认为您必须显示更多代码。如何将实现 ICustomFormatter 的对象传递给 Logger?您是否自定义对象实现 IFormattable
  • @Banshee 顺便说一句。您实现的代码看起来很奇怪(在格式化程序中查找目标配置)。认为您应该创建一个新问题,询问如何达到最终目标。认为您正在通过一条非常奇怪的道路到达目的地,并与您不应该战斗的障碍作斗争。
  • 默认情况下,只有时间、操作和客户端将被记录到磁盘,但我需要能够在运行时设置是否要添加参数以及它们应该被序列化的深度。因为它需要在运行时,所以每次都需要读取设置。我想我可以修复它,所以每次通话只读取一次,但这不是现在重要的部分。在这一点上,我只需要一个 rout,我可以指出这个特定的记录器将根据特定的方法格式化它的输出。
猜你喜欢
  • 2015-09-28
  • 2017-04-20
  • 2014-10-19
  • 1970-01-01
  • 2019-01-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多