try...catchを使用せずにログ出力する方法（NLog）

# 概要
本記事では`try...catchを使用せずにログ出力する方法`について解説いたします。また今回のログ出力のライブラリには[NLog](https://nlog-project.org/)を採用しております。

初めにログやライブラリについての説明を行うため、説明が不要な方、お忙しい方は[導入手順](#導入手順) に進んでください。

# ログがなぜ必要であるか
1. ソフトウェアの開発を行う上で例外（エラー）が発生したときに、その原因を特定するための検討材料としてログが必要になる。
2. 例外の詳細をファイル形式で出力することで情報の共有の難度を下げることができる。（特に開発からアプリケーションのテスト段階に移行した際に、テスト実施者とバグ修正者の間で情報の共有を取りやすくすることができる）
3. アプリケーションのリリース後の運用にてトラブルが発生してシステムがダウンしてしまうと最悪業務を停止することになってしまい、ログが吐き出されていなければ保守に遅れが生じてしまい多大な損失を被ることになります。

# try...catch
よくあるログの出力方法として以下の様にtry...catchを使用して例外を捕捉、ログを出力するという方法があります。（[NLogチュートリアル](https://github.com/nlog/nlog/wiki/Tutorial)のWriting log messagesを参照）

```diff_c_sharp
try {
   // 例外が発生する可能性のある処理
} catch (例外の型 変数) {
   // 例外が発生した場合に実行する処理
}
```

この様に普通に考えるのであれば発生した例外を捕捉してログを出力するにはcatch句の中にロガーを仕込めばよいのですが、その際の問題点（デメリット）として以下の様な事が考えられます。

1. 関数などでネストが深くなると例外を下位のcatch句から上位のcatch句に渡すために再スローを繰り返さなくてはならない。
2. システムの設計として、その関数を使用した際に起こりうる例外を適切な例外判定（Null参照やオーバーフロー等）を用いて捕捉することが望ましい。
3. try...catch地獄

なので今回はtry...catchを基本的には使用せずにログを出力したいと思います。

# ライブラリの選定
.NET FrameWorkのログ出力で主なライブラリには`log4net`と`NLog`というライブラリが存在しております。

|ライブラリ|Google検索結果件数|備考|
|----|----|----|
|log4net|約322,000件|開発休止宣言を行っており今後脆弱性に対応することがない|
|NLog|約298,000件|公式のドキュメントが読みやすく拡張性にも優れている|

この様に`log4net`の方が情報量は豊富ですが、`log4net`は開発休止宣言を行っており今後脆弱性に対応する可能性が少ないため今回は`NLog`を採用いたしました。（2021/12/9時点では開発休止宣言の記事が削除されているのか該当の記事を確認することができませんでした。ただ`NLog`の公式ドキュメントは手厚く拡張性にも優れているため、今後の流れとしてもこちらを採用するのがベターになりそうです）

# 導入手順
csprojファイルに以下の設定を追加します。

```c#:csproj
<PackageReference Include="NLog" Version="4.7.9" />
<PackageReference Include="NLog.Config" Version="4.7.9" />
<PackageReference Include="NLog.Web.AspNetCore" Version="4.12.0" />
```

プロジェクトのルートにエラーログの詳細設定ファイル（nlog.config）を生成します。fileName以降にはログの出力先を各々の環境に合わせて設定してください。layout以降にはログに出力したい例外の情報を設定することができます。（詳しい設定方法はNLogの[Configuration file](https://github.com/NLog/NLog/wiki/Configuration-file)を参照してください）

```c#: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="..\..\..\..\Log\Log-${shortdate}.log" layout="${longdate}|${level:uppercase=true}|${logger}|${threadid}|${message}|${exception:format=tostring}" />
    <target name="traceLog" xsi:type="File" fileName="..\..\..\..\Log\TraceLog\TraceLog-${shortdate}.log" layout="${longdate}|${level:uppercase=true}|${logger}|${threadid}|${message}|${exception:format=tostring}" />
    <target name="debugLog" xsi:type="File" fileName="..\..\..\..\Log\DebugLog\DebugLog-${shortdate}.log" layout="${longdate}|${level:uppercase=true}|${logger}|${threadid}|${message}|${exception:format=tostring}" />
    <target name="infoLog" xsi:type="File" fileName="..\..\..\..\Log\InfoLog\InfoLog-${shortdate}.log" layout="${longdate}|${level:uppercase=true}|${logger}|${threadid}|${message}|${exception:format=tostring}" />
    <target name="warnLog" xsi:type="File" fileName="..\..\..\..\Log\WarnLog\WarnLog-${shortdate}.log" layout="${longdate}|${level:uppercase=true}|${logger}|${threadid}|${message}|${exception:format=tostring}" />
    <target name="errorLog" xsi:type="File" fileName="..\..\..\..\Log\ErrorLog\ErrorLog-${shortdate}.log" layout="${longdate}|${level:uppercase=true}|${logger}|${threadid}|${message}|${exception:format=tostring}" />
    <target name="fatalLog" xsi:type="File" fileName="..\..\..\..\Log\FatalLog\FatalLog-${shortdate}.log" layout="${longdate}|${level:uppercase=true}|${logger}|${threadid}|${message}|${exception:format=tostring}" />
  </targets>

  <rules>
    <logger name="*" minlevel="Trace" writeTo="traceLog" />
    <logger name="*" minlevel="Debug" writeTo="debugLog" />
    <logger name="*" minlevel="Info" writeTo="infoLog" />
    <logger name="*" minlevel="Warn" writeTo="warnLog" />
    <logger name="*" minlevel="Error" writeTo="errorLog" />
    <logger name="*" minlevel="Fatal" writeTo="fatalLog" />
  </rules>
</nlog>
```

最後にプロジェクトのメインにてNLogを使用するための設定を以下のように追加いたします。

```diff_c_sharp:Program.cs
using System;
using NLog.Web;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Hosting;

public static void Main(string[] args)
{
  CreateHostBuilder(args).Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
  Host.CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(webBuilder =>
    {
      webBuilder.UseStartup<Startup>();
    })
+   .ConfigureLogging(logging =>
+   {
+     logging.ClearProviders();
+     logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
+   })
+   .UseNLog();
```

以上で全ての設定が完了となります。アプリケーションを実行して例外が発生しますとnlog.configで設定されたパスにエラーログが出力されるようになります。