web-dev-qa-db-ja.com

ELKスタックの互換性を維持しながら、NLogを使用して不特定のオブジェクトをファイルに記録する方法は?

ELGスタックと互換性のあるフォーマットでファイルにNSpecがunspecific(DataContracts)パラメーターを含む通信データをログに記録するのに数週間も苦労しています。実行時に構成する必要があり、出力パラメーターをMAX文字または深さのように制限できる場合は、これをお勧めします。

NLogには組み込みのJSONシリアライザーがありますが、デフォルトなしでプロパティのみを読み取り、 here を見るとわかるようにフィールドは無視されます。私のデータモデルを適合させることは大きな仕事であり、私はその正しい方法を実際には考えていません。NLogはデータモデルの外観に影響を与えるべきではありません。

カスタムJSONシリアライザー を追加する方法はいくつかあります。

  1. 次のように、各クラス(Datacontract)でSetupSerializationを使用できます。

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

私はすべての通信データをログに記録したいので、データモデル全体を登録する必要があり、その巨大な仕事で効果がありません。

  1. カスタムIValueFormatterを使用することもできますが、通信NLogインスタンスだけに追加することはできません。すべてのロガーにグローバルに追加する必要があります これのように

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

したがって、IValueFormatterは、通信ロガーからのデータのみを操作するようにフィルタリングする必要があります。おそらく、データをどこから取得するかをIValueFormatterに通知するフラグを使用して、クラスでデータをラップする必要があります。ただし、最適なソリューションのようには思えません。

また、実際にNLogがValueFormatterフィルターのデータを出力するようにする問題もあります hereValueFormatterは引き続き実行されますが、ファイルに含まれる通常のNLog JSONデータです。

NLogに必要なのはこれです。

  • パラメータを含むすべての通信データを、オブジェクトからフォーマットされた文字列に変換して、ELKスタックで読み取れるようにします。
  • データのオーバーフローを回避するためのパラメーターのシリアル化の深さまたは文字列の最大長
  • 実行時にNLog.configから構成可能(NLogと同様)
  • 特定のNLoggerインスタンスのみに影響します

私のデータはIParameterInspectorを介して受信され、パラメーター(型オブジェクト)も保持する特別なCallInformationクラスにコンパイルされます。パラメータは、複数のレイヤーで複雑に変化する可能性があります。 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>

何が欠けていますか?私のニーズをよりよくサポートできる別のログライブラリはありますか?

2
Banshee

私はロルフの提案が最高だと思います-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);
        }        
3
Julian