是否可以在没有模板化消息的情况下使用 NLog 的结构化日志记录?
Is it possible to use NLog's structured logging without having templated messages?
直到今天,我们一直在使用 NLog 4.4.12 版(没有结构化日志记录)。然而,我们使用 https://www.nuget.org/packages/NLog.StructuredLogging.Json/ 进行结构化日志记录。
使用此扩展的好处是您不需要模板化消息(包含索引或占位符供您额外 parameters/object(s) 记录)。该消息不包含您要记录的其他对象(即匿名类型)的任何索引或占位符。
切换到支持开箱即用的结构化日志记录的 NLog 4.6.5,我们希望摆脱那个额外的 NuGet 包。然而,我们的附加参数仅在使用带有实际 indexed/named 占位符的 templated 消息时才会被记录。
我们的消息中没有索引或占位符会导致我们的额外 parameters/object 不会通过 JSON 呈现出来。
是否可以使用非模板化消息,但仍然使用 NLog 的结构化日志记录我们的附加参数,这些参数已传递给它们以添加到 JSON?
下面是一个示例(请注意,我们在 nlog 周围使用了额外的包装器)
NLog 版本: 4.6.5
平台:.Net 4.5
当前 NLog 配置
// Arrange
var typeUsingLogger = typeof(NLogWrapperTest);
var nLogWrapper = new NLogWrapper(typeof(NLogWrapper));
var level = (LogLevel)Enum.Parse(typeof(LogLevel), nLevel.Name);
var message = $"{Guid.NewGuid()}"; // {{extendedLogProperties}} {{@extendedLogProperties}} {{@purchase}} {{badplaceholder}}
var innerException = new DivideByZeroException("bla inner exception");
var exception = new ArgumentNullException("bla out exception", innerException);
var extendedLogProperties = new
{
ClientID = 8,
MyOtherProp = "abc",
MySubObject = new
{
//nested object although not recommended
A = 123,
B = "yep"
}
};
//log configuration
var logConfig = new LoggingConfiguration();
var memoryTarget = new MemoryTarget("MemoryTarget");
var jsonLayout = new JsonLayout
{
IncludeAllProperties = true,
Attributes =
{
new JsonAttribute("dateTime", "${date:universalTime=true:format=o}" ),
new JsonAttribute("level", "${level:uppercase=true}" ),
new JsonAttribute("logger", "${logger}" ),
new JsonAttribute("message", "${message}" ),
new JsonAttribute("callsite", "${callsite:className=true:methodName=true:skipFrame=0}" ),
new JsonAttribute("exception", "${exception:format=ToString:innerFormat=ToString}" ),
new JsonAttribute("machinename", "${machinename}" ),
new JsonAttribute("processid", "${processid}" ),
new JsonAttribute("threadid", "${threadid}" ),
new JsonAttribute("threadname", "${threadname}" ),
new JsonAttribute("application", "${application}" ),
new JsonAttribute("aspnetSessionId", "${aspnet-sessionid}" ),
new JsonAttribute("iisSiteName", "${iis-site-name}" ),
new JsonAttribute("stage", "${stage}" ),
}
};
memoryTarget.Layout = jsonLayout;
logConfig.AddTarget("memoryTarget", memoryTarget);
var memoryTargetLoggingRule = new LoggingRule("*", nLevel, memoryTarget);
logConfig.LoggingRules.Add(memoryTargetLoggingRule);
LogManager.Configuration = logConfig;
// Act
nLogWrapper.Log(level, message, typeUsingLogger, exception, extendedLogProperties);
var jsonLogMsg = memoryTarget.Logs[0];
Assert.Matches("ClientID", jsonLogMsg);
Why do we need it?
在没有任何替换索引或占位符的情况下保持消息不变真是太好了,这样我们就可以在日志中搜索完全相同的消息。 (使用 new JsonAttribute("message", "${message:raw=true}"
不是一个选项)
同样,通过这种方式,我们最终不会在日志消息中出现一次 JSON 序列化对象(替换模板消息的 placeholders/indexes)和额外的 JSON 这些附加参数的字段。
请查看其最佳实践:https://github.com/justeat/NLog.StructuredLogging.Json/blob/master/README.md#best-practices
如果你问:"Why don't you continue using the NuGet NLog extension?"
答案是,当在嵌套对象的模板消息中使用 {@placeholder} 时,NLog 的结构化日志记录会更好地呈现附加参数。
编辑 1:
我希望我的匿名对象的所有属性都呈现在 json 的根目录中。如:
{
...
"ClientID": 8,
"MyOtherProp": "abc",
"MySubObject": {
"A": 123,
"B": "yep"
},
...
}
我认为您正在寻找 logger.WithProperty
。
示例:
var extendedLogProperties = new
{
ClientID = 8,
MyOtherProp = "abc",
MySubObject = new
{
//nested object although not recommended
A = 123,
B = "yep"
}
};
logger.WithProperty("extendedLogProperties", extendedLogProperties).Info("test message");
您可以将其序列化为 JSON、XML 等
示例,JSON 具有所有属性
将所有事件属性呈现为 JSON
配置:
<target xsi:type="File" name="jsonFile" 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="2"/>
</attribute>
</layout>
</target>
重要的是 includeAllProperties="true"
和 maxRecursionLimit="2"
(默认为 0
)。参见 Json Layout docs
这应该呈现(格式很好的演示,但将一行一行):
{
"time": "2019-06-18 11:09:00.2349",
"level": "Info",
"message": "test message",
"eventProperties": {
"extendedLogProperties": {
"ClientID": 8,
"MyOtherProp": "abc",
"MySubObject": {
"A": 123,
"B": "yep"
}
}
}
}
备注
所以要清楚:
The JSON will be written on one line, so no newlines.
在我的包装器中,我设法像这样实现它:
private void Log(
NLog.LogLevel nlogLogLevel,
Logger nlogLogger,
Type typeUsingLogger,
string message,
Exception exception,
IDictionary<string, object> additionalProperties = null)
{
var logEventInfo = new LogEventInfo(nlogLogLevel, typeUsingLogger.ToString(), null, message, new object[] { }, exception);
if (additionalProperties != null)
{
foreach (var x in additionalProperties)
{
if (!logEventInfo.Properties.ContainsKey(x.Key))
{
logEventInfo.Properties.Add(x.Key, x.Value);
}
}
}
nlogLogger.Log(_logWrapperType, logEventInfo);
}
并将 includeAllProperties 设置为 true 以及将 maxRecursionLimit 设置得更高 (2)
直到今天,我们一直在使用 NLog 4.4.12 版(没有结构化日志记录)。然而,我们使用 https://www.nuget.org/packages/NLog.StructuredLogging.Json/ 进行结构化日志记录。
使用此扩展的好处是您不需要模板化消息(包含索引或占位符供您额外 parameters/object(s) 记录)。该消息不包含您要记录的其他对象(即匿名类型)的任何索引或占位符。
切换到支持开箱即用的结构化日志记录的 NLog 4.6.5,我们希望摆脱那个额外的 NuGet 包。然而,我们的附加参数仅在使用带有实际 indexed/named 占位符的 templated 消息时才会被记录。
我们的消息中没有索引或占位符会导致我们的额外 parameters/object 不会通过 JSON 呈现出来。
是否可以使用非模板化消息,但仍然使用 NLog 的结构化日志记录我们的附加参数,这些参数已传递给它们以添加到 JSON?
下面是一个示例(请注意,我们在 nlog 周围使用了额外的包装器)
NLog 版本: 4.6.5
平台:.Net 4.5
当前 NLog 配置
// Arrange
var typeUsingLogger = typeof(NLogWrapperTest);
var nLogWrapper = new NLogWrapper(typeof(NLogWrapper));
var level = (LogLevel)Enum.Parse(typeof(LogLevel), nLevel.Name);
var message = $"{Guid.NewGuid()}"; // {{extendedLogProperties}} {{@extendedLogProperties}} {{@purchase}} {{badplaceholder}}
var innerException = new DivideByZeroException("bla inner exception");
var exception = new ArgumentNullException("bla out exception", innerException);
var extendedLogProperties = new
{
ClientID = 8,
MyOtherProp = "abc",
MySubObject = new
{
//nested object although not recommended
A = 123,
B = "yep"
}
};
//log configuration
var logConfig = new LoggingConfiguration();
var memoryTarget = new MemoryTarget("MemoryTarget");
var jsonLayout = new JsonLayout
{
IncludeAllProperties = true,
Attributes =
{
new JsonAttribute("dateTime", "${date:universalTime=true:format=o}" ),
new JsonAttribute("level", "${level:uppercase=true}" ),
new JsonAttribute("logger", "${logger}" ),
new JsonAttribute("message", "${message}" ),
new JsonAttribute("callsite", "${callsite:className=true:methodName=true:skipFrame=0}" ),
new JsonAttribute("exception", "${exception:format=ToString:innerFormat=ToString}" ),
new JsonAttribute("machinename", "${machinename}" ),
new JsonAttribute("processid", "${processid}" ),
new JsonAttribute("threadid", "${threadid}" ),
new JsonAttribute("threadname", "${threadname}" ),
new JsonAttribute("application", "${application}" ),
new JsonAttribute("aspnetSessionId", "${aspnet-sessionid}" ),
new JsonAttribute("iisSiteName", "${iis-site-name}" ),
new JsonAttribute("stage", "${stage}" ),
}
};
memoryTarget.Layout = jsonLayout;
logConfig.AddTarget("memoryTarget", memoryTarget);
var memoryTargetLoggingRule = new LoggingRule("*", nLevel, memoryTarget);
logConfig.LoggingRules.Add(memoryTargetLoggingRule);
LogManager.Configuration = logConfig;
// Act
nLogWrapper.Log(level, message, typeUsingLogger, exception, extendedLogProperties);
var jsonLogMsg = memoryTarget.Logs[0];
Assert.Matches("ClientID", jsonLogMsg);
Why do we need it?
在没有任何替换索引或占位符的情况下保持消息不变真是太好了,这样我们就可以在日志中搜索完全相同的消息。 (使用
new JsonAttribute("message", "${message:raw=true}"
不是一个选项)同样,通过这种方式,我们最终不会在日志消息中出现一次 JSON 序列化对象(替换模板消息的 placeholders/indexes)和额外的 JSON 这些附加参数的字段。
请查看其最佳实践:https://github.com/justeat/NLog.StructuredLogging.Json/blob/master/README.md#best-practices
如果你问:"Why don't you continue using the NuGet NLog extension?" 答案是,当在嵌套对象的模板消息中使用 {@placeholder} 时,NLog 的结构化日志记录会更好地呈现附加参数。
编辑 1: 我希望我的匿名对象的所有属性都呈现在 json 的根目录中。如:
{
...
"ClientID": 8,
"MyOtherProp": "abc",
"MySubObject": {
"A": 123,
"B": "yep"
},
...
}
我认为您正在寻找 logger.WithProperty
。
示例:
var extendedLogProperties = new
{
ClientID = 8,
MyOtherProp = "abc",
MySubObject = new
{
//nested object although not recommended
A = 123,
B = "yep"
}
};
logger.WithProperty("extendedLogProperties", extendedLogProperties).Info("test message");
您可以将其序列化为 JSON、XML 等
示例,JSON 具有所有属性
将所有事件属性呈现为 JSON
配置:
<target xsi:type="File" name="jsonFile" 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="2"/>
</attribute>
</layout>
</target>
重要的是 includeAllProperties="true"
和 maxRecursionLimit="2"
(默认为 0
)。参见 Json Layout docs
这应该呈现(格式很好的演示,但将一行一行):
{
"time": "2019-06-18 11:09:00.2349",
"level": "Info",
"message": "test message",
"eventProperties": {
"extendedLogProperties": {
"ClientID": 8,
"MyOtherProp": "abc",
"MySubObject": {
"A": 123,
"B": "yep"
}
}
}
}
备注
所以要清楚:
The JSON will be written on one line, so no newlines.
在我的包装器中,我设法像这样实现它:
private void Log(
NLog.LogLevel nlogLogLevel,
Logger nlogLogger,
Type typeUsingLogger,
string message,
Exception exception,
IDictionary<string, object> additionalProperties = null)
{
var logEventInfo = new LogEventInfo(nlogLogLevel, typeUsingLogger.ToString(), null, message, new object[] { }, exception);
if (additionalProperties != null)
{
foreach (var x in additionalProperties)
{
if (!logEventInfo.Properties.ContainsKey(x.Key))
{
logEventInfo.Properties.Add(x.Key, x.Value);
}
}
}
nlogLogger.Log(_logWrapperType, logEventInfo);
}
并将 includeAllProperties 设置为 true 以及将 maxRecursionLimit 设置得更高 (2)