NLogでログをSlackに通知する

# 概要

[NLog](https://nlog-project.org/)でロギングを仕込んだ.NETアプリケーションでエラーが発生した際に通知が届くようにしたい。
こういった場合お手軽な方法としてはメール通知が挙げられると思うが、メールを送信するためのメールサーバを用意するのが面倒。
他に良さそうな方法ないかなーと調べたところ、[NLogをSlackに出力するパッケージ](https://github.com/eth0izzle/NLog.Slack)を見つけたので、それを利用してNLogのログをSlackに通知するようにしたメモです。

# 環境

- Windows 10
- VisualStudio 2022
- .NET 6
- NLog 4.7.13
- NLog.Slack 2.0.0
- NLogはインストール＆構成済み

# NLog.Slackのインストール

VisualStudioの「Nugetパッケージの管理」か「パッケージマネージャコンソール」から「NLog.Slack」をインストールします。

```
Install-Package NLog.Slack
```

# Slack側の準備

SlackへはIncomming Webhookを利用してログを出力するので、Incomming Webhook用のアプリを追加します。
手順は[公式ドキュメント](https://api.slack.com/messaging/webhooks)の通りですが、一応簡単に記載しておきます。

https://api.slack.com/ の「Create an app」→「From scratch」と進んで、適当なアプリ名とワークスペースを選択してアプリを追加。

![nlog-slack-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197871/6d7fce50-8d8e-976d-c895-bffed1a56829.png)
![nlog-slack-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197871/09f59e5b-91a7-f2df-e055-148c6fe4f3dd.png)

「Features」→「App Home」からDisplay Nameとusernameを設定。（これを行わないとアプリがワークスペースに追加できないので）

![nlog-slack-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197871/f5ba78cd-200f-6a8b-9f04-ad88bf692178.png)

「Features」→「Incomming Webhooks」からIncomming Webhookを有効化して、下部の「Add New Webhook to Workspace」からワークスペースにアプリを追加。

![nlog-slack-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197871/87ad86f4-4d54-4c9f-b3d3-1ac1e93fd58f.png)
![nlog-slack-5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197871/42fab752-ab5e-3b54-b7d8-1b1551e22e8a.png)

後はWebhook URLをコピーすればSlack側の準備は以上です。

![nlog-slack-6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197871/0815446b-2d36-abdb-b91a-3e275327714c.png)

# NLog.Slackの構成

既に構成済みの`NLog.config`を以下として、NLog.SlackのターゲットとルールをNLog.configに追加します。

```xml:NLog.config(Slack構成前)
<?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 xsi:type="Console" name="logconsole"
				layout="${longdate}|${level}|${message} |${all-event-properties} ${exception:format=tostring}" />
		</target>
	</targets>

	<rules>
		<logger name="*" minlevel="Trace" writeTo="logconsole" />
	</rules>
</nlog>
```

```xml:NLog.config(Slack構成後)
<?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" >

	<!-- NLog.Slackの拡張を使用 -->
	<extensions>
		<add assembly="NLog.Slack" />
	</extensions>

	<targets>
		<target xsi:type="Console" name="logconsole"
				layout="${longdate}|${level}|${message} |${all-event-properties} ${exception:format=tostring}" />
		<!-- NLog.Slackのターゲット -->
		<target xsi:type="Slack" name="logslack"
                layout="${longdate}|${level}|${message} |${all-event-properties} ${exception:format=tostring}"
                webHookUrl="https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
                compact="true" />
	</targets>

	<rules>
		<logger name="*" minlevel="Trace" writeTo="logconsole" />
		<!-- 全てのログをSlackに通知 -->
		<logger name="*" minlevel="Trace" writeTo="logslack" />
	</rules>
</nlog>
```

`webHookUrl`には[Slack側の準備](#Slack側の準備)で作成したアプリのWebhook URLを指定します。

構成後、アプリケーションで何らかのログが書き出された際にSlackに通知が飛ぶようになります。

```csharp
logger.Info("NLogのログです。");
```

![nlog-slack-7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197871/a2c4baa3-ac47-7376-f7d1-a4e634df53e6.png)

実際に運用する際は全てのログがSlackに通知されてしまうとノイジーなので、Error以下（Error, Fatal）のログのみ通知させるのが良いと思います。

```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" >
...
	<rules>
		...
		<logger name="*" minlevel="Error" writeTo="logslack" />
	</rules>
</nlog>
```

# 通知メッセージのレイアウト

`target`の`compact`属性を使用することで通知メッセージのレイアウトを変更することが可能です。
[NLog.Slackの構成](#NLog.Slackの構成)では省略形式(`compact=true`)を指定してログのレイアウトメッセージのみを通知してますが、`compact=false`を指定すると、ログレベルに応じた色付けやプロセス情報等が付加できます。

```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 xsi:type="Slack" name="logslack"
                layout="${longdate}|${level}|${message} |${all-event-properties} ${exception:format=tostring}"
                webHookUrl="https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
                compact="false">
			<field name="Machine Name" layout="${machinename}" />
			<field name="Process Name" layout="${processname}" />
			<field name="Process PID" layout="${processid}" />
		</target>
	</targets>
</nlog>
```

![nlog-slack-8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197871/0f4e58ea-3a9c-2d4d-2e82-64c6a0b70d52.png)

任意の項目も追加できます。

```xml
<field name="Field Name" layout="layout" />
```

# `targets`の`async=true` について

[NLog.Slackのドキュメント](https://github.com/eth0izzle/NLog.Slack#nlogconfig)によると、Webhookに失敗した場合やタイムアウトした場合にアプリケーションのパフォーマンスが劣化するため、`targets`に`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">
		...
	</targets>
	...
</nlog>
```

しかしこれを行った場合、アプリケーションが例外で不正終了する直前に書き出したログがSlackに通知されないため、その辺りの注意が必要です。

```csharp
try
{
    // 何らかの例外が発生する可能性がある処理
}
catch(Exception ex)
{
    logger.Error(ex); // このログがasync="true"だとSlackに通知されない
    throw; // キャッチされずにアプリケーションが終了する
}
```

今回Slackに通知したかったアプリケーションの場合、「パフォーマンスは重要ではないが、エラーに気づけない可能性は出来るだけ排除したい」という性質だったのでログ出力は同期的に（`async="false"`で）行うようにしました。

# 参考文献

- [Sending messages using Incoming Webhooks | Slack](https://api.slack.com/messaging/webhooks)
- [eth0izzle/NLog.Slack](https://github.com/eth0izzle/NLog.Slack)