Bot Builder v4 : Application Insights を駆使した利用状況分析

＊2019/7/27 Web からダウンロードできるサンプルが変わっていたため、こちらの記事もアップデートします。

前回は Bot Connector のボット利用状況分析を紹介しましたが、今回から、より詳細な利用状況を分析するために、Application Insights を駆使する方法を紹介します。

[参考: Telemetry Logging](https://github.com/microsoft/botframework-solutions/blob/4.4/docs/reference/analytics/telemetrylogging.md)

今回は「基本ボット (C#)」で動作をためしました。事前に Bot Web App を作成してソースをダウンロードしてください。

# ApplicationInsights の組み込み

NuGet パッケージとして公開されている [Microsoft.Bot.Builder.Integration.ApplicationInsights.Core](https://github.com/microsoft/botbuilder-dotnet/tree/4.4/libraries/integration/Microsoft.Bot.Builder.Integration.ApplicationInsights.Core) を使ってボットに Application Insights を組み込みます。

1\. プロジェクトの NuGet パッケージ管理より、Microsoft.Bot.Builder.Integration.ApplicationInsights.Core を追加。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/6e47ca1a-9be6-eacf-37cf-cd272006a470.png)

2\. Startup.cs の ConfigureServices メソッドで、ダイアログやボットを追加する前に以下コードを追加。また Configuration プロパティが規定で存在しないため、プロパティとしても追加。

```csharp:startup.cs
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
    Configuration = configuration;
}

public void ConfigureServices(IServiceCollection services)
{
    ...
    // Add Application Insights 
    services.AddBotApplicationInsights(Configuration);
    ...
}
```

3\. appsettings.json に ApplicationInsights の項目を追加。自身のキーを指定。

```json:appsettings.json
{
  ...
  "ApplicationInsights": {
    "InstrumentationKey": "2953fafa-ed74-431b-a25c-93ab2fda29ef" 
  }
}
```

以上で Application Insights の設定は完了です。

## AddBotApplicationInsights で何をしているか

AddBotApplicationInsights は他のパターンの引数を取ることもできます。

[GitHub: AddBotApplicationInsights](https://github.com/microsoft/botbuilder-dotnet/blob/5d443a03d6e4368f4f9c969c684a092af1927de0/libraries/integration/Microsoft.Bot.Builder.Integration.ApplicationInsights.Core/ServiceCollectionExtensions.cs#L74)

メソッドを見てわかる通り、```IBotTelemetryClient``` を IoC に登録しています。

```csharp
public static IServiceCollection AddBotApplicationInsights(this IServiceCollection services, IConfiguration config)
{
    if (config == null)
    {
        throw new ArgumentNullException(nameof(config));
    }

    string instrumentationKey = config.GetValue<string>("ApplicationInsights:instrumentationKey");

    CreateBotTelemetry(services);

    IBotTelemetryClient telemetryClient = null;
    if (!string.IsNullOrWhiteSpace(instrumentationKey))
    {
        services.AddApplicationInsightsTelemetry(instrumentationKey);
        telemetryClient = new BotTelemetryClient(new TelemetryClient());
    }
    else
    {
        telemetryClient = NullBotTelemetryClient.Instance;
    }

    services.AddSingleton(telemetryClient);

    return services;
}
```

また他のミドルウェアやイニシャライザーも登録。これらのコードでユーザーから受けた情報を加工して Application Insights に渡すデータを作っています。

```csharp

        private static void CreateBotTelemetry(IServiceCollection services)
        {
            // Enables Bot Telemetry to save user/session id's as the bot user id and session
            services.AddTransient<TelemetrySaveBodyASPMiddleware>();
            services.AddSingleton<ITelemetryInitializer, OperationCorrelationTelemetryInitializer>();
            services.AddSingleton<ITelemetryInitializer, TelemetryBotIdInitializer>();
        }
```
ただここのコードを見てわかるとおり、これだけでは Application Insights に対して情報を送るコードはありません。

# LUIS の結果を Application Insights に送る

サンプルでは LUIS のクエリ結果を取るために ```LuisRecognizer``` を使いますが、引数に ```TelemetryClient``` を渡すことで解析できるようになります。

1\. FlightBookingRecognizer.cs で TelemetryClient を使うように変更。

- コンストラクタで IBotTelemetryClient を受け取る
- LuisRecognizer にオプションとして BotTelemetryClient を渡す

```csharp:LuisHelper.cs
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.AI.Luis;
using Microsoft.Extensions.Configuration;

namespace Microsoft.BotBuilderSamples
{
    public class FlightBookingRecognizer : IRecognizer
    {
        private readonly LuisRecognizer _recognizer;

        public FlightBookingRecognizer(IConfiguration configuration, IBotTelemetryClient telemetryClient)
        {
            var luisIsConfigured = !string.IsNullOrEmpty(configuration["LuisAppId"]) && !string.IsNullOrEmpty(configuration["LuisAPIKey"]) && !string.IsNullOrEmpty(configuration["LuisAPIHostName"]);
            if (luisIsConfigured)
            {
                var luisApplication = new LuisApplication(
                    configuration["LuisAppId"],
                    configuration["LuisAPIKey"],
                    "https://" + configuration["LuisAPIHostName"]);
                // LuisRecognizer を作成する際、TelemetryClient を指定。
                _recognizer = new LuisRecognizer(luisApplication, new LuisPredictionOptions() { TelemetryClient = telemetryClient });                
            }
        }

        // Returns true if luis is configured in the appsettings.json and initialized.
        public virtual bool IsConfigured => _recognizer != null;

        public virtual async Task<RecognizerResult> RecognizeAsync(ITurnContext turnContext, CancellationToken cancellationToken)
            => await _recognizer.RecognizeAsync(turnContext, cancellationToken);

        public virtual async Task<T> RecognizeAsync<T>(ITurnContext turnContext, CancellationToken cancellationToken)
            where T : IRecognizerConvert, new()
            => await _recognizer.RecognizeAsync<T>(turnContext, cancellationToken);
    }
}
```

2\. Dialog を実行している DialogBot.cs で渡された Dialog に対して TelemetryClient を設定。

- コンストラクタで IBotTelemetryClient を受け取る

```csharp:DialogBot.cs_コンストラクタ
public DialogBot(ConversationState conversationState, UserState userState, T dialog, ILogger<DialogBot<T>> logger, IBotTelemetryClient telemetryClient)
{
    ConversationState = conversationState;
    UserState = userState;
    Dialog = dialog;
    Logger = logger;
    dialog.TelemetryClient = telemetryClient;
}
```

3\. DialogBot を継承している DialogAndWelcomeBot.cs でも同様にコンストラクタに設定。

```csharp:DialogAndWelcomeBot.cs
public DialogAndWelcomeBot(ConversationState conversationState, UserState userState, T dialog, ILogger<DialogBot<T>> logger, IBotTelemetryClient telemetryClient)
    : base(conversationState, userState, dialog, logger, telemetryClient)
{
}
```

※以前のサンプルに含まれていた DialogExtension.cs は v4.4 からライブラリに統合されました。
[GitHub: DialogExtensions](https://github.com/microsoft/botbuilder-dotnet/blob/4.4/libraries/Microsoft.Bot.Builder.Dialogs/DialogExtensions.cs)

## LuisRecognizer でのロギング

[LuisRecognizer](https://github.com/microsoft/botbuilder-dotnet/blob/4.4/libraries/Microsoft.Bot.Builder.AI.LUIS/LuisRecognizer.cs) は ```ITelemetryRecognizer``` を継承し、[TelemetryClient.TrackEvent](
https://github.com/microsoft/botbuilder-dotnet/blob/5d443a03d6e4368f4f9c969c684a092af1927de0/libraries/Microsoft.Bot.Builder.AI.LUIS/LuisRecognizer.cs#L206) にて自然言語処理の解析結果を Application Insights に保存しています。

## 結果の解析

結果は Application Insights をクエリすればすぐに分かります。

1\. Azure Portal からログ先の Application Insights を開き、「分析」をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/7bc50bad-e05f-d90a-1268-3e28de2b28a7.png)

2\. [GitHub: Application Insights Analytics](https://github.com/microsoft/botframework-solutions/blob/4.4/docs/reference/analytics/applicationinsights.md) にあるクエリを実行。

```shell
customEvents
| where timestamp >= ago(24h)
| where name startswith "LuisResult"
| extend intent = tostring(customDimensions.['intent'])
| summarize count() by intent
| order by count_ desc
| render piechart
```
3\. 結果を確認。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/fdef13fa-6273-a5d3-5b35-7c246922cef1.png)

# TelemetryLoggerMiddleware

ユーザーからのメッセージ受信時や送信時にログするためには、TelemetryLoggerMiddleware ミドルウェアを使います。

1\. AdapterWithErrorHandler.cs に TelemetryLoggerMiddleware を追加

- IBotTelemetryClient をコンストラクタで受け取る
- Use を使ってミドルウェアを追加

```csharp:AdapterWithErrorHandler.cs
using System;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace Microsoft.BotBuilderSamples
{
    public class AdapterWithErrorHandler : BotFrameworkHttpAdapter
    {
        public AdapterWithErrorHandler(IConfiguration configuration, ILogger<BotFrameworkHttpAdapter> logger, IBotTelemetryClient telemetryClient, ConversationState conversationState = null)
            : base(configuration, logger)
        {
            OnTurnError = async (turnContext, exception) =>
            {
                ...
            };

            Use(new TelemetryLoggerMiddleware(telemetryClient));
        }
    }
}
```

2\. ボットを利用

## 結果の解析

1\. 分析で以下のクエリを実行。以下はイベント毎の解析。

```shell
customEvents
| where timestamp >= ago(24h)
| where name startswith "BotMessage"
| summarize count() by name, bin(timestamp, 10m)
| render timechart
```
2\. 結果を確認。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/69b7b55a-082f-6b03-0520-22a8aec44992.png)

3\. チャネル毎の場合

```shell
customEvents
| where timestamp >= ago(1d)
| where name startswith "BotMessage"
| extend channelId = tostring(customDimensions.['channelId'])
| summarize count() by channelId, bin(timestamp, 10m)
| render timechart
```

4\. 結果を確認。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/3e8205d3-f8d0-3a9b-5e68-7db7cae70be2.png)

# その他のロギング

[GitHub: telemetrylogging](https://github.com/microsoft/botframework-solutions/blob/4.4/docs/reference/analytics/telemetrylogging.md) にもある通り、Custom Events として各種イベントが取得できます。

- 外部依存サービスのトラック
- ウォーターフォールダイアログの実行状況

クエリは [GitHub: applicationinsights](https://github.com/microsoft/botframework-solutions/blob/master/docs/reference/analytics/applicationinsights.md) を参照してください。

# まとめ

Application Insights を統合することで詳細が確認できるようになり、より詳しく利用状況が見えるようになりました。次回は Power BI でその状況を見てみます。

[次の記事](https://qiita.com/kenakamu/items/a03f50529c1b5ef18803)
[目次に戻る](https://qiita.com/kenakamu/items/6dc043cfc1f199032883)

[サンプルコード](https://github.com/kenakamu/botbuilderv4completeguide/tree/appinsight)