NuGetを検索するとAzure Table Storage用のTargetをいくつか見かけますが、.NET Coreへの対応状況や依存ライブラリの最新化状況などの制約があって微妙です。
また、Azure Table StorageまわりのSDKはCosmosDBに切り出されたりしてややこしいので、たかがEntityの追加のみで依存関係を増やしたくありません。
そんなわけで、NLog標準のWebServiceTarget
を使った書き込みを試したら結構ハマりどころがあったので共有します。
事前準備
コンソールアプリのプロジェクトを作成
ターゲットフレームワークはNLogがサポートするものであれば何でも良いですが、.NET Core 2.0〜3.0までで動作確認をしました。
NLogのインストール
NuGetからインストールします。この記事では4.6.8の利用を前提としていますが、多少前後しても問題ないと思います。
Azure StorageのSAS URLの作成
SASとはShared Access Signatureの略で、あらかじめリソースやそれに対するアクション(参照、書き込みなど)、利用可能期間を制限したトークンです。これを含むURLを使うことで、リクエスト都度署名をするといった手間も省けるためWebServiceTargetで利用する上では好都合というわけです。
作成は簡単で、Azure Portalで対象ストレージアカウントを選択し、メニューの「Shared Access Signature」に移動します。対象リソースなどを選んで「SASと接続文字列を作成する」ボタンを押下後、表示された「Table service の SAS URL」を控えておいてください。
NLogの設定
まずは以下の通りWebServiceTarget
を設定します。名前は何でもいいですが、ここではAZTBLTarget
としてみました。
<?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"
throwExceptions="true">
<targets>
<target name="AZTBLTarget" xsi:type="WebService" protocol="JsonPost" url="<SAS URL>">
<parameter name="PartitionKey" layout="${callsite}" />
<parameter name="RowKey" layout="${ticks}.${sequenceid:padCharacter=0:padding=6}" />
<parameter name="LocalTimestamp" layout="${date:format=yyyy-MM-ddTHH\:mm\:ss.fffffff}" />
<parameter name="Level" layout="${level:uppercase=true}" />
<parameter name="Callsite" layout="${callsite}" />
<parameter name="Message" layout="${message}" />
<parameter name="Error" layout="${exception:format=Message, ToString:separator=\n}" />
</target>
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="AZTBLTarget" />
</rules>
</nlog>
簡単に説明すると、ターゲットの種類としてWebService
のものを定義していて、その通信手続きとしてJSONデータのPOST、そのJSONのパラメータとしてPartitionKey
やRowKey
を含む7項目を送信するようにしています。なおPartitionKey
とRowKey
はTable Storageとして必須の項目です。
さて、説明の中でurl
アトリビュートの説明を飛ばしていましたが、ここがハマりポイントです。コピペしてurl
に貼り付けるだけではうまくいきません。先の手順で取得したSAS URLを、以下の通り修正する必要があります。
https://accountname.table.core.windows.net/?&sv=2019-02-02&ss=t&srt=sco&sp=wau&se=2030-01-04T08:21:42Z&st=2020-01-04T00:21:42Z&spr=https&sig=xxxxxxxxx%3D
https://accountname.table.core.windows.net/nlogtable?$format=application/json&sv=2019-02-02&ss=t&srt=sco&sp=wau&se=2030-01-04T08:21:42Z&st=2020-01-04T00:21:42Z&spr=https&sig=xxxxxxxxx%3D
修正内容は以下の通り。
- URIの末尾に
/<テーブル名>
をつける。当たり前ですが、コピペでは動かず一瞬「ウッ」てなります。修正後の例は、テーブル名をnlogtable
のケースです。 -
&
を&
に置換。これもXMLに一般的な話だと思いますが、エラーになるので小心者の私は「えっえっ」てなりました。 -
$format=application/json
をつける。Insert Entityの仕様を見る限りAccept
ヘッダーは任意なのですが、なぜかこれを渡さないと415
エラーとなります。しかしながらNLogのWebServiceTargetではなぜかHTTPヘッダーにAccept
を設定することができませんでした。諦めかけていたところ、Payload format for Table service operationsを読んだら$format
で指定できることを発見したため、これをURLに含んでやります。
ログの書き込み
それでは実際に書き込んでみましょう。本記事のポイントは設定方法ですので、書き込み自体は一般的な手順に従えばOKかと思います。ちょっとハマるかもしれないポイントとしては、最終行のLogManager.Shutdown();
を実行しないと、ログ送信前にプロセスが終了してしまいTable Storgeに書き込まれない場合があるところでしょうか。
using NLog;
namespace NLogToAzureTable
{
class Program
{
static void Main(string[] args)
{
// NLog.configを出力ディレクトリにコピーするように設定してください。
// もしくは、以下の通り明示的にパスを指定することもできます。2つめの引数falseはエラーを無視するか否かの設定です(false=無視しない)
// LogManager.Configuration = new XmlLoggingConfiguration("/path/to/NLog.config", false);
// ロガーの初期化・取得
var logger = LogManager.GetCurrentClassLogger();
// ログの書き込み
logger.Info("test message for info");
logger.Warn("test message for warn");
// ロガーの終了。プロセスを終了する前にログを送信するために必要
LogManager.Shutdown();
}
}
}
非同期書き込みの利用
NLogには便利な非同期処理の仕組みAsyncWrapper
ターゲットがあります。ログメッセージをキューに貯めてバッチ的に別スレッドで処理してくれるといった便利なものです。
使い方は超簡単で、targets
のasync
アトリビュートにtrue
を設定するだけで全てのターゲットへの書き込みが非同期に行われます。
:中略
<targets async="true">
:中略
一部の書き込みのみを非同期にしたい場合は、ラッパーの名の通り非同期にしたいターゲットをAsyncWrapper
で囲みます。このとき、rule
に設定するname
もラッパーのものにすることに注意です。
<targets>
<target name="AZTBLTargetAsync" xsi:type="AsyncWrapper">
<target name="AZTBLTarget" xsi:type="WebService" protocol="JsonPost" url="<SAS URL>">
<parameter name="PartitionKey" layout="${callsite}" />
:中略
</target>
</target>
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="AZTBLTargetAsync" />
</rules>
詳細は公式Wikiを読んでみてください。
https://github.com/nlog/NLog/wiki/AsyncWrapper-target
補足
-
PartitionKey
に設定する値。記事では便宜上出力元メソッドの名前を指定していますが、ユーザーとかデバイスとかを一意に特定できるものを設定した方が調査しやすいと思います・ - パフォーマンスや負荷は未知数。
AsyncWrapper
を使うことでメイン処理への影響はないと思いますが、書き込み速度や負荷は計測していません。WebServiceTargetに一般的な議論かと思います。詳しい方アドバイスください!! - 出力レベル。そもそもリモートでログを取る時点で常にデバッグ情報が欲しいわけではないと思いますので、
minlevel
をエラーに設定するなど最適化が必要です。 - Logging Frameworkについて。実装ライブラリを自由に変更できるようにDIなフレームワークが.NET Coreでは用意されていますが、今回は未考慮です。
おまけ:UnityでNLogを使ってAzure Table Storageに書き込み
そもそも何でAzure Table Storageにログを書き込もうと思ったかというと、Gateboxというデバイス向けのアプリ開発をはじめたことがきっかけです。実機デバッグする際にコンソール出力やファイルファイルを選択できないことから、リモートにログ出力をする方法を探ることにしました。ログのためにサーバーを用意するのもアレなのでAzureに・・・という経緯です。
UnityのConsoleへのログ出力用ターゲットも同梱したファクトリーぽいものを作りましたのでよかったら使ってみてください。
NLogFactoryForUnity
https://github.com/uezo/NLogFactoryForUnity