こんにちは、サイオステクノロジー 佐藤 陽です。
今回は、Azure AzureFunctionsにおいてDIを使ってログを記録する時の注意点についてご紹介します。
はじめに
この問題に3ヵ月前くらいに直面し、時間を大幅に溶かしたのですが、
昨日また同じ問題に直面し、完全に解決方法が頭から抜け落ち、再度時間を溶かしてしまいました。
こんな愚かな過ちを二度と繰り返さないよう、備忘録として記事に残します。
なお解決策としては、公式のドキュメントや、しばやんさんのブログの中にも言及があるので、そちらもご覧ください。
AzureFunctionsでのログ出力の基本
今回想定しているのは、Azure Functionsで作成したアプリにおいて、LogAnalyticsにログを記録するようなケースです。
AzureFunctionsをApplication Insightと接続し、そのログの保管先をLog Analyticsに設定する、といった構成とします。
Azure Functionsにおいては、Loggerが提供されており、これを使う事で簡単にログを記録することができます。
例えば新規にHttpTriggerのAzureFunctionsを作成した場合、以下のソースコードをよく見るかと思います。
このコードにおける4行目や6行目の部分で、ログの記録を行っています。
[FunctionName("Function")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log) //←ILogger
{
log.LogInformation("C# HTTP trigger function processed a request."); //←ログを記録
string name = req.Query["name"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
string responseMessage = string.IsNullOrEmpty(name)
? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
: $"Hello, {name}. This HTTP triggered function executed successfully.";
return new OkObjectResult(responseMessage);
}
ただ、例えばこのFunctionクラスが呼び出す、別のインスタンスでログを記録したい場合、
ILoggerを順々に引き渡していく必要があります。
これは非常に手間ですよね。
そういった場合に、DIを使ってILoggerを注入することで実装が非常にシンプルになります。
今回はこのDIを使ったログ出力に関しするお話です。
private readonly ILogger<Function> _logger;
public Function(ILogger<Function> logger)
{
_logger = logger;
}
[FunctionName("Function")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log) //←ILogger
{
log.LogInformation("C# HTTP trigger function processed a request."); //←ログを記録
_logger.LogInformation("[DI]C# HTTP trigger function processed a request."); //←DIでログを記録
検証
実際にログを出力して、ログを確認していきたいと思います。
リソース準備
ではまず、ログの記録を確認するためにAzureリソースを作成していきます。
今回用意するものは以下3つです。
- Azure Functions
- Application Insights
- Log Analytics
さくっと作成していきましょう。
なお、Log Analytics→Application Insight→Azure Functionsの順番で作成していくと
リソース間の紐づけがしやすいかと思います。
作成が完了したら
1. Azure Functionsが正しいApplication Insightに接続されていること
2. Application Insightが正しいLog Analyticsに接続されていること
を確認します。
1. AzureFunctionsのApplication Insightsブレードから確認できます。
2. Application Insightの「プロパティ」ブレードの「ワークスペース」から確認できます。
実装
次に実装していきましょう。
とはいっても、サンプルコードを少し修正するだけです。
同じタイミングで似たようなLogを出力するよう実装しました。
DIを利用したロガーと、そうでないロガーでそれぞれ出力します。
実装出来たら、Azure Functionsにデプロイしてしまいましょう。
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace LogTestFunctions
{
public class Function
{
private readonly ILogger<Function> _logger;
public Function(ILogger<Function> logger)
{
_logger = logger;
}
[FunctionName("Function")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "logs")] HttpRequest req,
ILogger log) //←ILogger
{
string name = req.Query["name"];
log.LogInformation($"C# HTTP trigger function processed a request. query: {name}"); //←ログを記録
_logger.LogInformation($"[DI]C# HTTP trigger function processed a request. query: {name}"); //←DIでログを記録
return new OkResult();
}
}
}
動作確認
では動作確認していきます。
デプロイ済みの関数を実行します。
https://{function-name}.azurewebsites.net/api/logs?name=hoge
次に、先ほど作成したLog Analyticsにログが記録されていることを確認します。
tracesのテーブル全体を確認します。
すると
C# HTTP trigger function processed a request. query: hoge
のログだけ確認できました。
DIのロガーを使ったログが見られません。
この時、AzureFunctionsのモニターからログも見てみました。
こちらにはDIの方のログもあるようです。
どうやらApplication Insightとしては検知しているのですが、Log Analyticsとしては記録していないようです。
何故だ…、という事で私はむやみやたらにネットの海をさまよい、大幅に時間を溶かしてしまいました。
結果として、しっかりと公式ドキュメントに実装方法が記載されていました。
反省。
解決方法
解決方法としては、
【host.jsonに対して明示的にフィルターを追加する】
です。
ホストでは、ILogger<T> サービスと ILoggerFactory サービスがコンストラクターに挿入されます。 ただし、既定では、これらの新しいログ フィルターは関数のログから除外されます。 追加のフィルターおよびカテゴリを選択するには、host.json ファイルを変更する必要があります。
DIを使った場合は、明示的にログフィルターを追加する必要があるようです。
host.jsonを以下のように修正し、再度pushします。
なお、LogTestFunctionsというプロパティ名は対象のNamespace名なので、適宜自分の環境に合わせて修正してください。
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
}
},
"logLevel": {
"LogTestFunctions": "Information"
}
}
}
DIの方のログも正しく記録されていました!めでたし!!
まとめ
今回はAzure Functionsにおける、DIを使ったログ出力の注意点をご紹介しました。
既に他のサイトでも解説されており、それをなぞっただけの記事になりますが、誰かの役に立てれば幸いです。
あとは、しっかりと公式ドキュメントにも書かれていた内容なので
新規に利用する場合や、困った時には公式のドキュメントを見る大切さを改めて痛感しました。
なかなか読みづらいですが、しっかり読んでいきましょう!
ではまた!