概要
NLogでロギングを仕込んだ.NETアプリケーションでエラーが発生した際に通知が届くようにしたい。
こういった場合お手軽な方法としてはメール通知が挙げられると思うが、メールを送信するためのメールサーバを用意するのが面倒。
他に良さそうな方法ないかなーと調べたところ、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/ の「Create an app」→「From scratch」と進んで、適当なアプリ名とワークスペースを選択してアプリを追加。
「Features」→「App Home」からDisplay Nameとusernameを設定。(これを行わないとアプリがワークスペースに追加できないので)
「Features」→「Incomming Webhooks」からIncomming Webhookを有効化して、下部の「Add New Webhook to Workspace」からワークスペースにアプリを追加。
後はWebhook URLをコピーすればSlack側の準備は以上です。
NLog.Slackの構成
既に構成済みのNLog.config
を以下として、NLog.Slackのターゲットとルールを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="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 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側の準備で作成したアプリのWebhook URLを指定します。
構成後、アプリケーションで何らかのログが書き出された際にSlackに通知が飛ぶようになります。
logger.Info("NLogのログです。");
実際に運用する際は全てのログがSlackに通知されてしまうとノイジーなので、Error以下(Error, Fatal)のログのみ通知させるのが良いと思います。
<?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の構成では省略形式(compact=true
)を指定してログのレイアウトメッセージのみを通知してますが、compact=false
を指定すると、ログレベルに応じた色付けやプロセス情報等が付加できます。
<?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>
任意の項目も追加できます。
<field name="Field Name" layout="layout" />
targets
のasync=true
について
NLog.Slackのドキュメントによると、Webhookに失敗した場合やタイムアウトした場合にアプリケーションのパフォーマンスが劣化するため、targets
にasync=true
を追加してログ出力を非同期で行うのが推奨とのこと。
<?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に通知されないため、その辺りの注意が必要です。
try
{
// 何らかの例外が発生する可能性がある処理
}
catch(Exception ex)
{
logger.Error(ex); // このログがasync="true"だとSlackに通知されない
throw; // キャッチされずにアプリケーションが終了する
}
今回Slackに通知したかったアプリケーションの場合、「パフォーマンスは重要ではないが、エラーに気づけない可能性は出来るだけ排除したい」という性質だったのでログ出力は同期的に(async="false"
で)行うようにしました。