Можно ли использовать структурированное ведение журнала NLog без шаблонных сообщений?
До сегодняшнего дня мы использовали NLog версии 4.4.12 (без структурированной регистрации). Однако мы использовали https://www.nuget.org/packages/NLog.StructuredLogging.Json/ для структурированной регистрации.
Хорошая вещь об использовании этого расширения в том, что вам НЕ нужно иметь шаблонные сообщения (содержащие индексы или заполнители для ваших дополнительных параметров / объектов для регистрации). Сообщение не содержит индексов или заполнителей для ваших дополнительных объектов (например, анонимного типа) для регистрации.
Переходя к NLog 4.6.5, который поддерживает структурированное ведение журнала из коробки, мы хотели бы избавиться от этого дополнительного пакета NuGet. Однако наши дополнительные параметры регистрируются только при использовании шаблонных сообщений с фактическими индексированными / именованными заполнителями.
Отсутствие индекса или заполнителей в нашем сообщении не приводит к тому, что наши дополнительные параметры / объекты будут отображаться через 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);
Зачем нам это нужно?
Было бы очень хорошо, чтобы сообщение не изменялось без каких-либо замененных индексов или заполнителей, чтобы мы могли искать точно такое же сообщение в наших журналах. (с помощью
new JsonAttribute("message", "${message:raw=true}"
это не вариант)Также, таким образом, мы не заканчиваем тем, что сериализованные объекты JSON один раз в сообщении журнала (заменяющие заполнители / индексы шаблонного сообщения) И дополнительные поля JSON для этих дополнительных параметров.
Пожалуйста, ознакомьтесь с ее лучшими практиками: https://github.com/justeat/NLog.StructuredLogging.Json/blob/master/README.md#best-practices
Если вы спросите: "Почему бы вам не продолжить использовать расширение NuGet NLog?" Ответ заключается в том, что структурированное ведение журнала в NLog делает более приятными дополнительные параметры при использовании {@placeholder} в шаблонных сообщениях для вложенных объектов.
Редактировать 1: я хотел бы, чтобы все свойства моего анонимного объекта отображались в корне json. Такие как:
{
...
"ClientID": 8,
"MyOtherProp": "abc",
"MySubObject": {
"A": 123,
"B": "yep"
},
...
}
2 ответа
Я думаю, что вы ищете 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
Это должно отрисоваться (красиво отформатировано для демонстрации, но будет одной строкой):
{
"time": "2019-06-18 11:09:00.2349",
"level": "Info",
"message": "test message",
"eventProperties": {
"extendedLogProperties": {
"ClientID": 8,
"MyOtherProp": "abc",
"MySubObject": {
"A": 123,
"B": "yep"
}
}
}
}
Примечания
Итак, чтобы быть ясно,
JSON будет записан в одну строку, поэтому нет новых строк.
В моей обертке мне удалось понять это так:
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)