我为获得NLog来记录我的通信数据(包括不确定的(DataContracts)参数)以与ELK堆栈兼容的格式记录到文件而苦苦挣扎。如果可以限制输出参数(例如MAX字符或深度),则需要在运行时对其进行配置,并且首选使用它。
NLog具有内置的JSON序列化器,但它只会读取没有默认值的属性,正如您看到的here一样,字段将被忽略。适应我的数据模型将是一项艰巨的工作,而且我并不认为正确的做法是正确的,NLog不应影响数据模型的外观。
有两种添加custom JSON serializer的方法:
我可以像这样在每个类(Datacontract)上使用SetupSerialization:
LogManager.Setup().SetupSerialization(s =>
s.RegisterObjectTransformation<GetEntityViewRequest>(obj =>
return Newtonsoft.Json.Linq.JToken.FromObject(obj)
)
);
因为我要记录所有通信数据,所以必须注册整个数据模型,这是一项艰巨的工作,并且效果不佳。
我可以使用自定义IValueFormatter,但不能仅将其添加到我的通信NLog实例中,而必须将其全局添加到所有记录器中like this:
NLog.Config.ConfigurationItemFactory.Default.ValueFormatter = new NLogValueFormatter();
因此IValueFormatter
需要过滤,因此只能操作来自通讯记录器的数据。我可能需要将数据包装在带有标志的类中,该标志告诉IValueFormatter
数据来自何处,但是这并不是最佳解决方案。
如您所见,ValueFormatter
实际获得NLog来输出here过滤的数据也存在问题。 ValueFormatter
仍然可以运行,但是其常规NLog JSON数据将最终出现在文件中。
我从NLog需要的是这个:
我的数据通过IParameterInspector进入,它被编译成一个特殊的CallInformation类,该类还保存有parameters(类型对象)。这些参数可能会随多层变化而复杂。整个CallInforamtion对象像这样发送到NLog:
_comLogger.Log(LogLevel.Info, "ComLogger : {@callInfo}", callInfo);
Nlog.config现在看起来像这样:
<logger name="CommunicationLogger" minlevel="Trace" writeto="communicationFileLog"></logger>
<target xsi:type="File"
name="communicationFileLog"
fileName="${basedir}/logs/${shortdate}.log"
maxArchiveDays="5"
maxArchiveFiles="10">
<layout xsi:type="JsonLayout" includeAllProperties="true" maxRecursionLimit="1">
</layout>
</target>
我想念的是什么?是否有另一个日志库可以更好地满足我的需求?
我认为Rolf的建议是最好的-创建一个将使用JSON.NET的布局。那可以做所有花哨的技巧,例如序列化字段和处理[JsonIgnore]
。
最基本的版本将如下所示:
using Newtonsoft.Json;
using NLog;
using NLog.Config;
using NLog.Layouts;
namespace MyNamespace
{
[Layout("JsonNetLayout")]
[ThreadAgnostic] // different thread, same result
[ThreadSafe]
public class JsonNetLayout : Layout
{
public Formatting Formatting { get; set; } = Formatting.Indented;
/// <inheritdoc />
protected override string GetFormattedMessage(LogEventInfo logEvent)
{
if (logEvent.Properties == null)
{
return "{}";
}
return JsonConvert.SerializeObject(logEvent.Properties, Formatting);
}
}
}
和register the layout,我将使用:
Layout.Register<JsonNetLayout>("JsonNetLayout"); // namespace NLog.Layouts
配置可能看起来像这样:
<target xsi:type="File"
name="communicationFileLog"
fileName="${basedir}/logs/${shortdate}.log"
maxArchiveDays="5"
maxArchiveFiles="10">
<layout xsi:type="JsonNetLayout" />
</target>
记录此对象时:
public class ObjectWithFieldsAndJsonStuff
{
[JsonProperty]
private string _myField = "value1";
[JsonProperty("newname")]
public string FieldWithName { get; set; } = "value2";
[JsonIgnore]
public string IgnoreMe { get; set; } = "value3";
}
和此记录器调用:
logger
.WithProperty("prop1", "value1")
.WithProperty("prop2", objectWithFieldsAndJsonStuff)
.Info("Hi");
这将导致:
{
"prop1": "value1",
"prop2": {
"_myField": "value1",
"newname": "value2"
}
}
上面所有这些在单元测试中-使用xUnit
[Fact]
public void JsonNetLayoutTest()
{
// Arrange
Layout.Register<JsonNetLayout>("JsonNetLayout");
var xmlConfig = @"
<nlog xmlns=""http://www.nlog-project.org/schemas/NLog.xsd""
xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""
throwExceptions=""true"">
<targets>
<target xsi:type=""Memory"" name=""target1"" >
<layout xsi:type=""JsonNetLayout"" />
</target>
</targets>
<rules>
<logger name=""*"" minlevel=""Trace"" writeTo=""target1"" />
</rules>
</nlog>
";
LogManager.Configuration = XmlLoggingConfiguration.CreateFromXmlString(xmlConfig);
var logger = LogManager.GetLogger("logger1");
var memoryTarget = LogManager.Configuration.FindTargetByName<MemoryTarget>("target1");
// Act
var objectWithFieldsAndJsonStuff = new ObjectWithFieldsAndJsonStuff();
logger
.WithProperty("prop1", "value1")
.WithProperty("prop2", objectWithFieldsAndJsonStuff)
.Info("Hi");
// Assert
var actual = memoryTarget.Logs.Single();
var expected =
@"{
""prop1"": ""value1"",
""prop2"": {
""_myField"": ""value1"",
""newname"": ""value2""
}
}";
Assert.Equal(expected, actual);
}