.NET CoreのコンソールアプリからAzure Application Insightsにログを送信する

**本記事は.NET Core 3.x以上が対象です。**

# 前置き
C#erなので、小規模なツールやバッチ処理を行うプログラム等を.NET Coreコンソールアプリケーションで作ることがよくあります。
多くの場合、ログはファイルやたまにイベントログに出力したりします。
ただ、アプリケーションで発生したエラーを検知して通知等のアクションを行いたいような場合だと、色々準備する必要があり手間が掛かったりします。
なので、**もういっそコンソールアプリのログも全部Azure Application Insightsに送信してしまえばいいじゃん**、というのが今回の趣旨です。

# Application Insightsとは
私がくどくど書くまでもなく、Azureのドキュメントに書いてありますので、省略。
[Application Insights とは何か?](https://docs.microsoft.com/ja-jp/azure/azure-monitor/app/app-insights-overview)

# .NET CoreでApplication Insightsにログを送信する方法
[TelemetryClient](https://docs.microsoft.com/en-us/dotnet/api/microsoft.applicationinsights.telemetryclient?view=azure-dotnet)を使って自前で直接送信する方法と、ログプロバイダーを構築してILogger経由で送信する方法があります。
今回はテレメトリというより完全にログなので、後者を選択します。

# Application Insightsを作成
AzureポータルからApplication Insightsを作成してください。
Application Insightsにデータを送信するためには、作成後に概要画面に表示されるインストルメンテーションキーが必要となります。

# コードを書いてみる
[汎用ホスト](https://docs.microsoft.com/ja-jp/aspnet/core/fundamentals/host/generic-host)は使わずに構成をしてみます。
まずはnugetで [Microsoft.ApplicationInsights.WorkerService](https://www.nuget.org/packages/Microsoft.ApplicationInsights.WorkerService) をインストールします。
その後、以下のような感じでコードを書いてみました。

```C#
static async Task Main(string[] args)
{
    IServiceCollection services = new ServiceCollection();
    services.AddLogging(builder =>
    {
        // ログのフィルタを構成
        builder.AddFilter<Microsoft.Extensions.Logging.ApplicationInsights.ApplicationInsightsLoggerProvider>("", LogLevel.Information);
    });
    services.AddApplicationInsightsTelemetryWorkerService("<Replace with your instrumentation key>");

    // テレメトリチャネルをInMemoryChannelに設定
    services.Configure<TelemetryConfiguration>(
        (config) => { config.TelemetryChannel = new InMemoryChannel(); });

    // サービスプロバイダーを構築
    IServiceProvider serviceProvider = services.BuildServiceProvider();
    // Loggerを取得
    ILogger<Program> logger = serviceProvider.GetRequiredService<ILogger<Program>>();
    // テレメトリクライアントを取得
    var telemetryClient = serviceProvider.GetRequiredService<TelemetryClient>();

    try
    {
        logger.LogInformation("Program start.");
        await DoWork();
        logger.LogInformation("Program end.");
    }
    catch (Exception e)
    {
        logger.LogError(e, "Program error!");
    }
    finally
    {
        telemetryClient.Flush();
    }
}
```
コード内を見ていきます。

```C#
IServiceCollection services = new ServiceCollection();
services.AddLogging(builder =>
{
    // ログのフィルタを構成
    builder.AddFilter<Microsoft.Extensions.Logging.ApplicationInsights.ApplicationInsightsLoggerProvider>("", LogLevel.Information);
});
services.AddApplicationInsightsTelemetryWorkerService("<Replace with your instrumentation key>");
```
まず、ServiceCollectionを作成してログのフィルタを設定してログレベルをInformationに設定しています。
その後、Application Insightsのインストルメンテーションキーを渡して送信できるように構成させます。
<br/>

```C#:
// テレメトリチャネルをInMemoryChannelに設定
services.Configure<TelemetryConfiguration>(
    (config) => { config.TelemetryChannel = new InMemoryChannel(); });
```
「テレメトリチャネルをInMemoryChannelに設定」と書いている部分では、Application Insightsのテレメトリチャネルをデフォルトの物から変更しています。
何も指定しない場合、テレメトリチャネルはServerTelemetryChannelとなります。再試行に優れた高度なチャネルなのですが、テレメトリの送信が同期的には行われなくなります。ASP.NET Coreアプリケーションのような動作し続けている物なら問題ありませんが、コンソールアプリケーションのように明確に（割と素早く）終了するアプリケーションでは、送信が行われる前にプログラムが終了してしまい、ログが送られないままになってしまう場合が多いです。
InMemoryChannelはServerTelemetryChannelに比べて軽量かつ再試行性が劣りますが、メモリ内にバッファリングしているため同期的なテレメトリの送信が可能です。
<br/>

```C#
// サービスプロバイダーを構築
IServiceProvider serviceProvider = services.BuildServiceProvider();
// Loggerを取得
ILogger<Program> logger = serviceProvider.GetRequiredService<ILogger<Program>>();
// テレメトリクライアントを取得
var telemetryClient = serviceProvider.GetRequiredService<TelemetryClient>();
```
そして、上記の設定が終わったので、サービスプロバイダーを構築してLoggerインスタンスを取得し、同様にTelemetryClientを取得します。（当然、先ほど設定したInMemoryChannelが取得されます）

```C#
telemetryClient.Flush();
```
その後は適当にログを出力し、最後に上記で取得したTelemetryClientからFlushを呼び出してデータの送信を行って終了です。
上記の通りInMemoryChannelにしているため、Flushで同期的にテレメトリが送信されます。

# 出力されたログを確認してみる
AzureポータルのApplication Insightsから送信されたのログを見てみます。
監視カテゴリ内のログからLog Analyticsの画面を開き、tracesの内容を表示させます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150005/59b27987-2b9e-e2cd-5844-43bc98a5424b.png)

コード内で送信させたログメッセージが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150005/eab9d1b9-a0c5-3da5-7b86-515e30385e5f.png)

コード内で例外を発生させた場合も確認してみます。今度はexceptionsの内容を表示させます。
例外が記録されていることも確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150005/01c51494-9e17-4cfa-271f-54ef6d3558af.png)

# あとがき
割と少ないコード量でコンソールアプリからApplication Insightsにログが送れました。
ただ、このコードだけだとログのフィルタリング設定など、設定ファイル等で変更したいような内容までコード内で設定しているので、あまり使い勝手がよくありません。
このことは若干趣旨がずれるので、この辺を解消する方法は別途書きたいと思っています。


# 参考ページ
- .NET Core および ASP.NET Core でのログ記録
https://docs.microsoft.com/ja-jp/aspnet/core/fundamentals/logging

- ワーカー サービス アプリケーション (非 HTTP アプリケーション) 向け Application Insights
https://docs.microsoft.com/ja-jp/azure/azure-monitor/app/worker-service

- Application Insights のテレメトリ チャネル
https://docs.microsoft.com/ja-jp/azure/azure-monitor/app/telemetry-channels