c# - 如何使用 NLog 将未指定的对象记录到保持 ELK Stack 兼容性的文件中?
问题描述
数周以来,我一直在努力让 NLog 以与 ELK 堆栈兼容的格式记录我的通信数据,包括非特定(DataContracts)参数。它需要在运行时配置,如果输出参数可以限制为 MAX 字符或深度,则首选它。
NLog 有一个内置的 JSON 序列化器,但它只会读取没有默认值的属性,字段将被忽略,如您在此处看到的。调整我的数据模型将是一项艰巨的工作,我并不认为这是正确的方法,NLog 不应该影响数据模型的外观。
有几种方法可以添加自定义 JSON 序列化程序 :
我可以像这样在每个类(Datacontract)上使用SetupSerialization:
LogManager.Setup().SetupSerialization(s => s.RegisterObjectTransformation<GetEntityViewRequest>(obj => return Newtonsoft.Json.Linq.JToken.FromObject(obj) ) );
因为我想记录所有通信数据,所以必须注册整个数据模型,它的工作量很大,而且效率不高。
我可以使用自定义 IValueFormatter 但它不能仅添加到我的通信 NLog 实例中,它必须全局添加到所有记录器中,如下所示:
NLog.Config.ConfigurationItemFactory.Default.ValueFormatter = new NLogValueFormatter();
所以IValueFormatter
需要过滤所以它只是操纵来自通信记录器的数据。我可能需要将我的数据包装在一个带有标志的类中,该标志告诉IValueFormatter
数据来自哪里,但它感觉不是一个最佳解决方案。
ValueFormatter
如您在此处看到的,实际上让 NLog 输出过滤器的数据也存在问题。仍然运行,ValueFormatter
但它的常规 NLog JSON 数据将最终出现在文件中。
我需要的 NLog 是这样的:
- 将包括参数在内的所有通信数据从对象转换为格式化的字符串,以便 ELK 堆栈读取它。
- 参数上的序列化深度或字符串最大长度以避免数据溢出
- 可在运行时从 NLog.config 配置(与 NLog 一样)
- 仅影响特定的 NLogger 实例
我的数据通过 IParameterInspector 进入,它被编译成一个特殊的 CallInformation 类,该类也包含参数(类型对象)。参数可以随着多层而变得复杂。整个 CallInformation 对象像这样发送到 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 System.Collections.Generic;
using Newtonsoft.Json;
using NLog;
using NLog.Config;
using NLog.Layouts;
namespace MyNamespace
{
/// <summary>
/// Render all properties to Json with JSON.NET, also include message and exception
/// </summary>
[Layout("JsonNetLayout")]
[ThreadAgnostic] // different thread, same result
[ThreadSafe]
public class JsonNetLayout : Layout
{
public Formatting Formatting { get; set; } = Formatting.Indented; // This option could be set from the XML config
/// <inheritdoc />
protected override string GetFormattedMessage(LogEventInfo logEvent)
{
var allProperties = logEvent.Properties ?? new Dictionary<object, object>();
allProperties["message"] = logEvent.FormattedMessage;
if (logEvent.Exception != null)
{
allProperties["exception"] = logEvent.Exception.ToString(); //toString to prevent too much data properties
}
return JsonConvert.SerializeObject(allProperties, Formatting);
}
}
}
并注册布局,我将使用:
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"
},
"message": "Hi"
}
单元测试
以上所有这些都在单元测试中 - 使用 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""
},
""message"": ""Hi""
}";
Assert.Equal(expected, actual);
}
推荐阅读
- isabelle - 伊莎贝尔发展统计
- vb.net - .doc文件批量转换为.txt的更新方法
- vba - Microsoft Project - 防止字段自动计算
- mysql - 为什么我的数据库没有更新?(使用netbeans xampp mysql)
- scala - 如何在 Spark(使用 Scala)中用逗号替换空格?
- python - django-admin:在 HostGator 共享主机中找不到用于启动 django 项目的命令
- r - 编写一个函数以将另一个函数包含在回归模型中
- tensorflow - 将多 GPU 与 keras.utils.multi_gpu_model 一起使用时,SageMaker 失败
- r - R Lubridate 默认按字母顺序排列月份和工作日
- shell - 使用 shell 脚本删除一些文本?