为特定的 NLog 实例设置 ValueFormatter?
Set ValueFormatter for specific NLog instance?
我有 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
您可以确保要格式化的对象继承自IFormattable
。 https://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
NLog JsonLayout 有两个选项对 LogEvent 属性的序列化很重要:
- IncludeAllProperties
- 最大递归限制
默认参数是这样的:
<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
-已完全序列化的属性。
我们的想法是,人们不应该关心在记录日志时如何配置 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
)
);
我有 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
您可以确保要格式化的对象继承自IFormattable
。 https://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
NLog JsonLayout 有两个选项对 LogEvent 属性的序列化很重要:
- IncludeAllProperties
- 最大递归限制
默认参数是这样的:
<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
-已完全序列化的属性。
我们的想法是,人们不应该关心在记录日志时如何配置 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
)
);