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" ?><nlogxmlns="http://www.nlog-project.org/schemas/NLog.xsd"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"throwExceptions="true"><targets><targetname="AZTBLTarget"xsi:type="WebService"protocol="JsonPost"url="<SAS URL>"><parametername="PartitionKey"layout="${callsite}"/><parametername="RowKey"layout="${ticks}.${sequenceid:padCharacter=0:padding=6}"/><parametername="LocalTimestamp"layout="${date:format=yyyy-MM-ddTHH\:mm\:ss.fffffff}"/><parametername="Level"layout="${level:uppercase=true}"/><parametername="Callsite"layout="${callsite}"/><parametername="Message"layout="${message}"/><parametername="Error"layout="${exception:format=Message, ToString:separator=\n}"/></target></targets><rules><loggername="*"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に書き込まれない場合があるところでしょうか。
usingNLog;namespaceNLogToAzureTable{classProgram{staticvoidMain(string[]args){// NLog.configを出力ディレクトリにコピーするように設定してください。// もしくは、以下の通り明示的にパスを指定することもできます。2つめの引数falseはエラーを無視するか否かの設定です(false=無視しない)// LogManager.Configuration = new XmlLoggingConfiguration("/path/to/NLog.config", false);// ロガーの初期化・取得varlogger=LogManager.GetCurrentClassLogger();// ログの書き込みlogger.Info("test message for info");logger.Warn("test message for warn");// ロガーの終了。プロセスを終了する前にログを送信するために必要LogManager.Shutdown();}}}非同期書き込みの利用
NLogには便利な非同期処理の仕組みAsyncWrapperターゲットがあります。ログメッセージをキューに貯めてバッチ的に別スレッドで処理してくれるといった便利なものです。
使い方は超簡単で、targetsのasyncアトリビュートにtrueを設定するだけで全てのターゲットへの書き込みが非同期に行われます。
:中略
<targetsasync="true">:中略
一部の書き込みのみを非同期にしたい場合は、ラッパーの名の通り非同期にしたいターゲットをAsyncWrapperで囲みます。このとき、ruleに設定するnameもラッパーのものにすることに注意です。
<targets><targetname="AZTBLTargetAsync"xsi:type="AsyncWrapper"><targetname="AZTBLTarget"xsi:type="WebService"protocol="JsonPost"url="<SAS URL>"><parametername="PartitionKey"layout="${callsite}"/>:中略
</target></target></targets><rules><loggername="*"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