如何使用Nlog将未指定的对象记录到文件中,以维护ELK Stack兼容性?

问题描述 投票:1回答:1

我为获得NLog来记录我的通信数据(包括不确定的(DataContracts)参数)以与ELK堆栈兼容的格式记录到文件而苦苦挣扎。如果可以限制输出参数(例如MAX字符或深度),则需要在运行时对其进行配置,并且首选使用它。

NLog具有内置的JSON序列化器,但它只会读取没有默认值的属性,正如您看到的here一样,字段将被忽略。适应我的数据模型将是一项艰巨的工作,而且我并不认为正确的做法是正确的,NLog不应影响数据模型的外观。

有两种添加custom JSON serializer的方法:

  1. 我可以像这样在每个类(Datacontract)上使用SetupSerialization:

    LogManager.Setup().SetupSerialization(s =>
       s.RegisterObjectTransformation<GetEntityViewRequest>(obj => 
           return Newtonsoft.Json.Linq.JToken.FromObject(obj)
       )
    );
    

因为我要记录所有通信数据,所以必须注册整个数据模型,这是一项艰巨的工作,并且效果不佳。

  1. 我可以使用自定义IValueFormatter,但不能仅将其添加到我的通信NLog实例中,而必须将其全局添加到所有记录器中like this

    NLog.Config.ConfigurationItemFactory.Default.ValueFormatter = new NLogValueFormatter();
    

因此IValueFormatter需要过滤,因此只能操作来自通讯记录器的数据。我可能需要将数据包装在带有标志的类中,该标志告诉IValueFormatter数据来自何处,但是这并不是最佳解决方案。

如您所见,ValueFormatter实际获得NLog来输出here过滤的数据也存在问题。 ValueFormatter仍然可以运行,但是其常规NLog JSON数据将最终出现在文件中。

我从NLog需要的是这个:

  • 将所有通信数据(包括参数)从对象转换为格式化的字符串,以便ELK堆栈读取它。
  • 参数的序列化深度或字符串最大长度,以避免数据溢出
  • 可在运行时从NLog.config配置(因为NLog是)
  • 仅影响特定的NLogger实例

我的数据通过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>

我想念的是什么?是否有另一个日志库可以更好地满足我的需求?

c# logging elastic-stack nlog serilog
1个回答
0
投票

我认为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);
        }

© www.soinside.com 2019 - 2024. All rights reserved.