NLogの非同期出力とConfigファイルを使用しない方法

## 目的

・非同期にLogを出力する
ログ出力がボトルネックになって処理が遅延する場合がある。非同期にLog出力ができるNLogを使用した方法を検討する

・configファイルに依存しない作りにする。
複数のLogを出力したい場合が多い為、プログラムで動的に設定を変更できるように作成したい

## NLogをTLogでラッピング

TLogクラスを作成してDLLを作成した。より細かい設定は、引数を増やして設定すればよい。
Close処理も入れたが、プログラム終了時にNLogは自動でシャットダウンされる。

```C#:TLog.cs
   public static class  TLog
    {
        private static Dictionary<string,string> LoggerNameMap = new Dictionary<string,string>();

        private static NLog.Config.LoggingConfiguration config = new NLog.Config.LoggingConfiguration();

        // NLogレイアウト
        // https://nlog-project.org/config/?tab=layout-renderers


        /// <summary>
        /// ファイルフォーマットサンプル1
        /// 実行中のアプリケーションの基底ディレクトリ/logs/yyyy-MM-dd.log
        /// </summary>
        public static readonly string FileSampleFormat1 = "${basedir}/logs/${shortdate}.log";

        /// <summary>
        /// レイアウトフォーマットサンプル1
        /// 
        /// 例1 通常
        /// 2023-12-22 09:11:15.3232 TRACE [11] Logger.Logger - Trace ログです。
        /// 
        /// 例2 Exception
        /// 2023-12-22 09:09:36.7485 ERROR [10] Logger.Logger - エラーが発生しました。 System.DivideByZeroException: Attempted to divide by zero.
        /// at WinFormsApp1.Form1.thTest() in D:\study\work\DotNet_Sample\ConsoleApp1\WinFormsApp1\Form1.cs:line 51
        /// 
        /// 
        /// </summary>
        public static readonly string LayoutSampleFormat1 = "${longdate} ${level:uppercase=true:padding=-5} [${threadid}] ${logger} - ${message} ${exception:format=tostring}";

        /*
            name - rules の出力先に設定するために任意の名前を指定します。
            xsi:type - ファイルへ出力するためのタイプ ”File” を指定します。
            encoding - ファイル出力のエンコーディングを指定します。
            writeBom - BOM(バイトオーダーマーク)の有り無しを指定します。 BOM有りだと文字コード判定が容易なので、エディタの文字化けが防げます。
            lineEnding - OSに合わせた改行コードが指定できます。Default(OSに合わせる), CR, CRLF, LF, None から選択します。
            layout - ログのフォーマットを指定します。必要に応じて編集してください。
            fileName - ログファイルの出力先を指定します。{processname} 変数でプロセス名をファイルに名にしています。
            archiveFileName - バックアップ先のファイル名を指定します。{###} 変数は数字の連番になります。
            archiveEvery - 指定の時間でログファイルを切り替えます。Day, Hour, Minute, Month, None などから選択します。
            archiveNumbering - 連番の番号の付け方を Rolling, Sequence から選択します。
            archiveAboveSize - 最大ファイルサイズ
            maxArchiveFiles - 保存するアーカイブファイルの最大数を指定します。デフォルトは 0 です。        
         
         */



        /// <summary>
        /// Logインスタンスを取得する
        /// </summary>
        /// <param name="LoggerName">Logger名</param>
        /// <param name="logger">オブジェクト</param>
        /// <returns>結果</returns>
        public static bool GetInstance(string LoggerName, ref NLog.Logger logger)
        {
            if(LoggerNameMap.ContainsKey(LoggerName))
            {
                logger = NLog.LogManager.GetLogger(LoggerNameMap[LoggerName]);
                return true;
            }
            else
            {
                return false;
            }

        }


        /// <summary>
        /// 非同期のLogを作成する
        /// パフォーマンスを優先する場合は、非同期での出力を行う
        /// </summary>
        /// <param name="LoggerName">Logger名称</param>
        /// <param name="FileName">ファイルのフルパス(相対or絶対)</param>
        /// <param name="levMin">出力レベル Min</param>
        /// <param name="levMax">出力レベル Max</param>
        /// <param name="layout">レイアウト</param>
        public static void MakeLogAsync(string LoggerName, string FileName, LogLevel levMin,LogLevel levMax,string layout = "${longdate} ${level:uppercase=true:padding=-5} [${threadid}] ${logger} - ${message} ${exception:format=tostring}")
        {

            if (LoggerNameMap.ContainsKey(LoggerName))
            {
                return;
            }

            var logfile = new NLog.Targets.FileTarget(LoggerName);

            // ファイル名の設定
            logfile.FileName = FileName;
            // ファイルサイズ
            logfile. ArchiveAboveSize  = 1000000;
            // バックアップファイル数
            logfile.MaxArchiveFiles = 10;
            // バックアアップファイル名設定
            logfile.ArchiveNumbering = ArchiveNumberingMode.Sequence;
            // レイアウト
            logfile.Layout = layout;

            AsyncTargetWrapper wrapper = new AsyncTargetWrapper();

            wrapper.WrappedTarget = logfile;
            wrapper.QueueLimit = 5000;
            wrapper.OverflowAction = AsyncTargetWrapperOverflowAction.Discard;


            config.AddRule(levMin, levMax, wrapper, LoggerName + "_Logger");

            NLog.LogManager.Configuration = config;

            LoggerNameMap.Add(LoggerName, LoggerName + "_Logger");

        }


        /// <summary>
        /// Logを作成する
        /// </summary>
        /// <param name="LoggerName">Logger名称</param>
        /// <param name="FileName">ファイルのフルパス(相対or絶対)</param>
        /// <param name="levMin">出力レベル Min</param>
        /// <param name="levMax">出力レベル Max</param>
        /// <param name="layout">レイアウト</param>
        public static void MakeLog(string LoggerName, string FileName, LogLevel levMin, LogLevel levMax, string layout = "${longdate} ${level:uppercase=true:padding=-5} [${threadid}] ${logger} - ${message} ${exception:format=tostring}")
        {

            if (LoggerNameMap.ContainsKey(LoggerName))
            {
                return;
            }

            var logfile = new NLog.Targets.FileTarget(LoggerName);

            // ファイル名の設定
            logfile.FileName = FileName;
            // ファイルサイズ
            logfile.ArchiveAboveSize = 1000000;
            // バックアップファイル数
            logfile.MaxArchiveFiles = 10;
            // バックアアップファイル名設定
            logfile.ArchiveNumbering = ArchiveNumberingMode.Sequence;
            // レイアウト
            logfile.Layout = layout;


            config.AddRule(levMin, levMax, logfile, LoggerName + "_Logger");

            NLog.LogManager.Configuration = config;

            LoggerNameMap.Add(LoggerName, LoggerName + "_Logger");

        }


        /// <summary>
        /// 終了処理
        /// アプリケーション終了時に実行する
        /// </summary>
        public static void Close()
        {

            NLog.LogManager.Shutdown();

            LoggerNameMap.Clear();

        }

    }

```
### サンプルアプリ

Sample1.logに6000回出力
Sample2.logに6000回出力
Sample3.logに実行時間を出力

```C#:WinFormsApp1.cs
// ボタン処理以外は省略

        private void button1_Click(object sender, EventArgs e)
        {
            Logger.TLog.MakeLogAsync("Sample1", "${basedir}/logs/Sample1_Async_${shortdate}.log", LogLevel.Trace, LogLevel.Fatal, Logger.TLog.LayoutSampleFormat1);
            Logger.TLog.MakeLog("Sample2", "${basedir}/logs/Sample2_${shortdate}.log", LogLevel.Trace, LogLevel.Fatal, Logger.TLog.LayoutSampleFormat1);
            Logger.TLog.MakeLog("Sample3", "${basedir}/logs/Sample3_${shortdate}.log", LogLevel.Trace, LogLevel.Fatal, Logger.TLog.LayoutSampleFormat1);


            NLog.Logger log1 = NLog.LogManager.GetCurrentClassLogger();
            NLog.Logger log2 = NLog.LogManager.GetCurrentClassLogger();
            NLog.Logger log3 = NLog.LogManager.GetCurrentClassLogger();

            Logger.TLog.GetInstance("Sample1", ref log1);
            Logger.TLog.GetInstance("Sample2", ref log2);
            Logger.TLog.GetInstance("Sample3", ref log3);

            System.Threading.Thread.Sleep(200);

            Stopwatch sw = new Stopwatch();

            sw.Start();

            for(int i = 0; i < 6000; i++)
            {
                log1.Trace("{0} Trace ログです。",i);
            }

            sw.Stop();

            log3.Trace("log1 経過時間:{0}", sw.ElapsedMilliseconds);

            sw.Reset();

            sw.Start();

            for (int i = 0; i < 6000; i++)
            {
                log2.Trace("{0} Trace ログです。", i);
            }

            sw.Stop();

            log3.Trace("log2 経過時間:{0}", sw.ElapsedMilliseconds);

            sw.Reset();

        }

```

### 出力結果

``` Sample1_Async_2023-12-22.0.log
2023-12-22 15:29:47.4872 TRACE [5] Sample1_Logger - 0 Trace ログです。 
2023-12-22 15:29:47.4872 TRACE [5] Sample1_Logger - 1 Trace ログです。 
2023-12-22 15:29:47.4872 TRACE [5] Sample1_Logger - 2 Trace ログです。 
2023-12-22 15:29:47.4872 TRACE [5] Sample1_Logger - 3 Trace ログです。 
2023-12-22 15:29:47.4872 TRACE [5] Sample1_Logger - 4 Trace ログです。 
2023-12-22 15:29:47.4872 TRACE [5] Sample1_Logger - 5 Trace ログです。 
2023-12-22 15:29:47.4872 TRACE [5] Sample1_Logger - 6 Trace ログです。 
2023-12-22 15:29:47.4872 TRACE [5] Sample1_Logger - 7 Trace ログです。 
2023-12-22 15:29:47.4872 TRACE [5] Sample1_Logger - 8 Trace ログです。 
2023-12-22 15:29:47.4872 TRACE [5] Sample1_Logger - 9 Trace ログです。 
.......
```

``` Sample1_Async_2023-12-22.0.log
2023-12-22 15:29:47.5204 TRACE [5] Sample2_Logger - 0 Trace ログです。 
2023-12-22 15:29:47.5384 TRACE [5] Sample2_Logger - 1 Trace ログです。 
2023-12-22 15:29:47.5384 TRACE [5] Sample2_Logger - 2 Trace ログです。 
2023-12-22 15:29:47.5384 TRACE [5] Sample2_Logger - 3 Trace ログです。 
2023-12-22 15:29:47.5384 TRACE [5] Sample2_Logger - 4 Trace ログです。 
2023-12-22 15:29:47.5384 TRACE [5] Sample2_Logger - 5 Trace ログです。 
2023-12-22 15:29:47.5384 TRACE [5] Sample2_Logger - 6 Trace ログです。 
2023-12-22 15:29:47.5384 TRACE [5] Sample2_Logger - 7 Trace ログです。 
2023-12-22 15:29:47.5384 TRACE [5] Sample2_Logger - 8 Trace ログです。 
2023-12-22 15:29:47.5384 TRACE [5] Sample2_Logger - 9 Trace ログです。 
.......
```
```Sample3_2023-12-22.log
2023-12-22 15:29:47.5019 TRACE [5] Sample3_Logger - log1 経過時間:23 
2023-12-22 15:29:47.5958 TRACE [5] Sample3_Logger - log2 経過時間:71 
2023-12-22 15:29:49.9777 TRACE [12] Sample3_Logger - log1 経過時間:8 
2023-12-22 15:29:50.0334 TRACE [12] Sample3_Logger - log2 経過時間:59 
2023-12-22 15:29:52.4479 TRACE [13] Sample3_Logger - log1 経過時間:6 
2023-12-22 15:29:52.5017 TRACE [13] Sample3_Logger - log2 経過時間:61 
2023-12-22 15:29:55.3623 TRACE [5] Sample3_Logger - log1 経過時間:5 
2023-12-22 15:29:55.4081 TRACE [5] Sample3_Logger - log2 経過時間:47 
2023-12-22 15:29:58.0483 TRACE [14] Sample3_Logger - log1 経過時間:6 
2023-12-22 15:29:58.0952 TRACE [14] Sample3_Logger - log2 経過時間:53 
2023-12-22 15:30:00.1110 TRACE [13] Sample3_Logger - log1 経過時間:5 
2023-12-22 15:30:00.1579 TRACE [13] Sample3_Logger - log2 経過時間:48 
2023-12-22 15:30:03.0522 TRACE [12] Sample3_Logger - log1 経過時間:4 
2023-12-22 15:30:03.0952 TRACE [12] Sample3_Logger - log2 経過時間:53 
2023-12-22 15:30:04.7734 TRACE [5] Sample3_Logger - log1 経過時間:4 
2023-12-22 15:30:04.8142 TRACE [5] Sample3_Logger - log2 経過時間:49 
2023-12-22 15:30:06.6026 TRACE [14] Sample3_Logger - log1 経過時間:6 
2023-12-22 15:30:06.6583 TRACE [14] Sample3_Logger - log2 経過時間:50 
2023-12-22 15:30:08.3455 TRACE [12] Sample3_Logger - log1 経過時間:4 
2023-12-22 15:30:08.3924 TRACE [12] Sample3_Logger - log2 経過時間:47 
2023-12-22 15:30:10.0843 TRACE [5] Sample3_Logger - log1 経過時間:6 
2023-12-22 15:30:10.1421 TRACE [5] Sample3_Logger - log2 経過時間:50 
2023-12-22 15:30:11.7989 TRACE [13] Sample3_Logger - log1 経過時間:5 
2023-12-22 15:30:11.8457 TRACE [13] Sample3_Logger - log2 経過時間:50 
```

### 結果
非同期で作成したSamlple1の方が終了が速いことが、分かった。
ファイルへの出力は、Samlple1の方が遅いかもしれないが、他の処理への遅延を与えない為には、非同期で動作させた方が効率が良いと思われる。