C# NLog チュートリアル

#0. 本稿について

NLogの[チュートリアル](https://github.com/NLog/NLog/wiki/Tutorial)を踏襲していく

#1. 環境 ~Environment~
+ Microsoft Visual Studio 2017 Community
+ .Net Framework 4.6.1
+ NLog 4.6.1
+ NLog.config 4.6.1

#2. 導入 ~Installing NLog~

NuGetパッケージの管理から__「NLog」__,__「NLog.Config」__をインストールする。
__「NLog.Config」__に__「NLog」__も同梱されているのでこのパッケージだけで問題ない。

```
Install-Package NLog.Config
```

#3. NLogの設定 ~Configure NLog Targets for output~

Configファイルで設定する方法と、ソースコードで設定する方法を記載した。どちらも同じ設定となる。本稿では設定ファイルでの方法を採用している。NLog.configパッケージをインストールすると、プロジェクトディレクトリにNLog.configファイルが作成されているので、内容を下記の通り書き換える。


####  設定ファイルの場合

```xml:NLog.config
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <targets>
    <target name="logfile" xsi:type="File" fileName="file.txt" />
    <target name="logconsole" xsi:type="Console" />
  </targets>

  <rules>
    <logger name="*" minlevel="Info" writeTo="logconsole" />
    <logger name="*" minlevel="Debug" writeTo="logfile" />
  </rules>
</nlog>
```

####  ソースコードの場合


```cs:
var config = new NLog.Config.LoggingConfiguration();

var logfile = new NLog.Targets.FileTarget("logfile") { FileName = "file.txt" };
var logconsole = new NLog.Targets.ConsoleTarget("logconsole");
            
config.AddRule(LogLevel.Info, LogLevel.Fatal, logconsole);
config.AddRule(LogLevel.Debug, LogLevel.Fatal, logfile);
            
NLog.LogManager.Configuration = config;
```

#4. ログファイルの書き出し(Writing log messages)

NLogをラップするNLogServiceクラスを作成し、ログレベル"Info"のログを出力してみる。

```NLogService.cs
using NLog;

namespace NLogService
{
    public static class NLogService
    {
        private static Logger logger = LogManager.GetCurrentClassLogger();

        public static void PrintInfoLog(string str)
        {
            logger.Info(str);
        }
    }
}
```



```Program.cs
namespace NLogService
{
    class Program
    {
        static void Main(string[] args)
        {
            NLogService.PrintInfoLog("Hello World");
            Console.ReadKey();
        }
    }
}
```

__[結果]__
 コンソールに下記のログメッセージが出力された。また、同じログメッセージを記載したfile.txtも出力された。

```
2019-04-01 23:19:07.5302|INFO|NLogService.NLogService|Hello World
```

##4-1. Loggerの指定

上記の通り、__NLog.LogManager.GetCurrentClassLogger()__を使用した場合、呼び出し側のクラス名でロガーを作成する。__NLog.LogManager.GetLogger（ "MyLogger"）__を呼び出した場合、明示的なロガーの名前を指定することも出来る。

下記のように、NLog.congigファイルを書き換えてみる。

```xml:NLog.config
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <targets>
    <target name="logfile" xsi:type="File" fileName="file.txt" />
    <target name="logconsole" xsi:type="Console" />
  </targets>

  <rules>
    <logger name="LEVEL_INFO" minlevel="Info" writeTo="logconsole" />
    <logger name="LEVEL_DEBUG" minlevel="Debug" writeTo="logfile" />
  </rules>
</nlog>
```

```Program.cs
namespace NLogService
{
    class Program
    {
        static void Main(string[] args)
        {
            var logger = NLog.LogManager.GetLogger("LEVEL_INFO");
            logger.Info("Hello_World");
            Console.ReadKey();
        }
    }
}
```

__[結果]__
コンソールに下記のログメッセージは表示されたが、ログファイルは出力されなかった。

##4-2. minLevelについて

__minLevel__で設定したログレベル以上のログメッセージを出力する。1つのロガーに書き込まれたメッセージは、__logging-rules__設定に基づいて複数のターゲットに出力する。下記の設定ファイルの場合、ログレベル__Info__以上の情報はコンソールに出力し、ログレベル__Error__以上の情報はログファイルに出力する。

```xml:NLog.config
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <targets>
    <target name="logfile" xsi:type="File" fileName="file.txt" />
    <target name="logconsole" xsi:type="Console" />
  </targets>

  <rules>
    <logger name="*" minlevel="Info" writeTo="logconsole" />
    <logger name="*" minlevel="Error" writeTo="logfile" />
  </rules>
</nlog>
```

##4-3. Log levels

ログレベルは、メッセージの重要度や詳細度を示します。

+ __Trace__ 
 + プロトコルのペイロードなど大量で詳細なデータを出力するときに使用する。開発中のみ有効
+ __Debug__
 + Traceレベルよりも詳細ではないデバック中のログを出力するときに使用する。開発中のみ有効
+ __Info__
 + 情報メッセージ。稼働環境で有効
+ __Warn__
 + 警告メッセージ。回復可能であるか、または一時的な障害に関する警告メッセージを出力する。
+ __Error__
 + エラーメッセージ。__Exseption__情報を出力する。
+ __Fatal__
 + 非常に重大なエラーメッセージ。

```MyClass.cs
public class MyClass
{
  private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();

  public void MyMethod1()
  {
    //各ログレベルの出力サンプル
    logger.Trace("Sample trace message");
    logger.Debug("Sample debug message");
    logger.Info("Sample informational message");
    logger.Warn("Sample warning message");
    logger.Error("Sample error message");
    logger.Fatal("Sample fatal error message");

    //またはLog()メソッドにログレベルとメッセージを渡すことで出力することが可能
    logger.Log(LogLevel.Info, "Sample informational message");


    // Exseption情報を出力する例
    try
    {
        //do something
    }
    catch (Exception ex)
    {
        logger.Error(ex, "ow noos!"); // render the exception with ${exception}
        throw;
    }
  }
}
```

##4-4. レイアウトとレイアウトレンダリング(Layouts and LayoutRenderers)

ログメッセージのレイアウトを設定することができる。下記のプログラムで出力するメッセージを比較する。

```Program.cs
namespace NLogService
{
    class Program
    {
        static void Main(string[] args)
        {
            NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
            Exception ex = new Exception();
            logger.Error(ex);
        }
    }
}
```

####4-4-1. デフォルトのシンプルレイアウト

```xml
<target name="logfile" xsi:type="File" fileName="file.txt" layout="${longdate}|${level:uppercase=true}|${logger}|${message}" />
```

####4-4-2. 出力メッセージ

```
2019-04-02 21:28:01.3018|ERROR|NLogService.Program|System.Exception: 種類 'System.Exception' の例外がスローされました。
```


####4-4-3. 詳細なログレイアウト

```xml
<target name="logfile" xsi:type="File" fileName="file.txt" layout="${longdate}|${level:uppercase=true}|${logger}|${threadid}|${message}|${exception:format=tostring}" />
```

####4-4-4. 出力メッセージ

```
2019-04-02 21:27:19.1566|ERROR|NLogService.Program|1|System.Exception: 種類 'System.Exception' の例外がスローされました。|System.Exception: 種類 'System.Exception' の例外がスローされました。
```

他にも[CSV](https://github.com/NLog/NLog/wiki/CsvLayout)や[JSON](https://github.com/NLog/NLog/wiki/JsonLayout)でのフォーマットが存在する。

# 5.効率の良いロギング ~Best Practices for Logging~

##5-1. ~Logger should be a static variable in each class~

__ロガーは各クラスの静的変数であるべきです。__新しくロガーを作成することでロックを取得してオブジェクトを割り当てる必要があるので、無駄な処理となります。そのため、下記のようにロガーをあらかじめ作成することが効率が良いです。

```cs
namespace MyNamespace
{
  public class MyClass
  {
    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
  }
}
```

##5-2. ~Logger should handle string formatting~

__ロガーに文字列フォーマットを操作させてください。__文字列の連結やフォーマット処理を直前に行わないでください。代わりにLoggerにフォーマット処理をさせてください。これにより無駄な処理を減らすことができます。下記のように実行してください。

```cs:
logger.Info("Hello {0}", "Earth");
```

##5-3. ~Logger should be given the Exception~

__ロガーに例外を与えてください。__ロガーに例外をフォーマットのパラメータとして与えないでください。代わりに下記のように、最初の引数として与えて下さい。

```cs:
try
{
}
catch (Exception ex)
{
    logger.Error(ex, "Something bad happened");
}
```

##5-4. Validate XML configuration from NLog.config

NLogはデフォルトですべての例外を飲み込むので、ログの出力中に問題が発生してもアプリケーションを終了することはありません。しかし、初期のNLogの設定に失敗した場合は、ロキング出来なくなるのでアプリケーションにとって致命的な問題です。そこで、__throwConfigExceptions = "true"__をNLog.Configに追加することで、設定に問題があった時にエラーを発生することが出来ます。


```xml:NLog.config
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      throwConfigExceptions="true">
</nlog>
```

※ __throwExceptions = "true"__という設定もありますがこれは本番環境では使用しないでください。アプリケーションに致命的な問題を与える可能性があります。これは、単体テストとローカルのトラブルシューティングを目的としています。

## 5-5 Remember to Flush

__手動でクローズしてください。__NLogはデフォルトでアプリケーションのシャットダウン時に自動的にクローズしようとします。Microsoft Windowsが提供する.NETアプリケーションではアプリケーションが終了するまでの時間（通常2秒）以内にシャットダウンすることが出来ます。ネットワークトラフィック(Http,Mail,Tcp)を利用するNLog設定の場合は、手動でクローズ/シャットダウンすることをお勧めします。


```cs:
NLog.LogManager.Shutdown(); // Flush and close down internal threads and timers
```

Mono/Linux上で実行している.NETアプリケーションではアプリケーションのシャットダウン時に上記の停止処理をする必要があります。処理を行わない場合、未処理の例外やセグメンテーション違反、その他予測不可能な動作が発生する可能性があります。

#6. ラッパー ~Wripper~

NLogではラッパーを設定することが出来ます。よく使われるWrapperは下記の通りです。

+ __AsyncWrapper__ 
 + 書き込みが非同期となる。
+ __BufferingWrapper__
 + ログをバッファでまとめて出力する。
+ __FallbackGroup__
 + エラー時にフォールバック機能を提供できる。
+ __RetryingWrapper__
 + 書き込みに失敗したら、再度書き込みを行う。

下記は__AsyncWrapperの短縮形「async="true"」__のサンプルとなります。
これによって、ファイルへのすべての書き込みが非同期となります。

```xml:NLog.Config
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <targets async="true">
        <target name="logfile" xsi:type="File" fileName="file.txt" />
    </targets>

    <rules>
        <logger name="*" minlevel="Info" writeTo="logfile" />
    </rules>
</nlog>
```

#99. 参考

[NLog Home](https://nlog-project.org/)