Debug a bot with inspection middleware

APPLIES TO: SDK v4

This article describes how to debug a bot using inspection middleware. This feature allows the Bot Framework Emulator to debug traffic into and out of the bot in addition to looking at the current state of the bot. You can use a trace message to send data to the Emulator and then inspect the state of your bot in any given turn of the conversation.

We use an EchoBot built locally using the Bot Framework v4 (C# | JavaScript | Python) to show how to debug and inspect the bot's message state. You can also Debug a bot using IDE or Debug with the Bot Framework Emulator, but to debug state you need to add inspection middleware to your bot. The Inspection bot samples are available here: C#, JavaScript and Python.

Prerequisites

Update your Emulator to the latest version

Before using the bot inspection middleware to debug your bot, you need to update your Emulator to be version 4.5 or newer. Check the latest version for updates.

To check the version of your Emulator, select Help > About in the menu. You will see the current version of your Emulator.

current version

Update your bot's code

Set up the inspection state and add the inspection middleware to the adapter in the Startup.cs file. The inspection state is provided through dependency injection. See the code update below or refer to the inspection sample here: C#.

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient().AddControllers().AddNewtonsoftJson();

    services.AddSingleton<IStorage, MemoryStorage>();

    // The Inspection Middleware needs some scratch state to keep track of the (logical) listening connections.
    services.AddSingleton<InspectionState>();

    // You can inspect the UserState
    services.AddSingleton<UserState>();

    // You can inspect the ConversationState
    services.AddSingleton<ConversationState>();

    // Create the Bot Framework Adapter.
    services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithInspection>();

    // Create the bot as a transient. In this case the ASP Controller is expecting an IBot.
    services.AddTransient<IBot, EchoBot>();
}

AdapterWithInspection.cs

public class AdapterWithInspection : CloudAdapter
{
    public AdapterWithInspection(IConfiguration configuration, IHttpClientFactory httpClientFactory, InspectionState inspectionState, UserState userState, ConversationState conversationState, ILogger<IBotFrameworkHttpAdapter> logger)
        : base(configuration, httpClientFactory, logger)
    {
        // Inspection needs credentiaols because it will be sending the Activities and User and Conversation State to the emulator
        var credentials = new MicrosoftAppCredentials(configuration["MicrosoftAppId"], configuration["MicrosoftAppPassword"]);

        Use(new InspectionMiddleware(inspectionState, userState, conversationState, credentials));

        OnTurnError = async (turnContext, exception) =>
        {
            // Log any leaked exception from the application.
            logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}");

            // Send a message to the user
            await turnContext.SendActivityAsync("The bot encountered an error or bug.");
            await turnContext.SendActivityAsync("To continue to run this bot, please fix the bot source code.");

            // Send a trace activity, which will be displayed in the Bot Framework Emulator
            await turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError");
        };
    }
}

Update the bot class in the EchoBot.cs file.

EchoBot.cs

private readonly ConversationState _conversationState;
private readonly UserState _userState;

public EchoBot(ConversationState conversationState, UserState userState)
{
    _conversationState = conversationState;
    _userState = userState;
}

public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
{
    await base.OnTurnAsync(turnContext, cancellationToken);

    await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
    await _userState.SaveChangesAsync(turnContext, false, cancellationToken);
}

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    var conversationStateProp = _conversationState.CreateProperty<CustomState>("customState");
    var convProp = await conversationStateProp.GetAsync(turnContext, () => new CustomState { Value = 0 }, cancellationToken);

    var userStateProp = _userState.CreateProperty<CustomState>("customState");
    var userProp = await userStateProp.GetAsync(turnContext, () => new CustomState { Value = 0 }, cancellationToken);

    await turnContext.SendActivityAsync(MessageFactory.Text($"Echo: {turnContext.Activity.Text} conversation state: {convProp.Value} user state: {userProp.Value}"), cancellationToken);

    convProp.Value++;
    userProp.Value++;
}

Test your bot locally

After updating the code you can run your bot locally and test the debugging feature using two Emulators: one to send and receive messages, and the other to inspect the state of messages in debugging mode. To test your bot locally take the following steps:

  1. Navigate to your bot's directory in a terminal and execute the following command to run your bot locally:

    dotnet run
    
  2. Open your Emulator. Click Open Bot. Fill in Bot URL with http://localhost:3978/api/messages and the MicrosoftAppId and MicrosoftAppPassword values. If you have a JavaScript bot you can find these values in your bot's .env file. If you have a C# bot you can find these values in the appsettings.json file. Click Connect.

  3. Now open another Emulator window. This second Emulator window will work as a debugger. Follow the instructions as described in the previous step. Check Open in debug mode and then click Connect.

  4. At this point you will see a command with a unique identifier (/INSPECT attach <identifier>) in your debugging Emulator. Copy the whole command with the identifier from the debugging Emulator and paste it into the chat box of the first Emulator.

    Note

    A unique identifier is generated every time when the Emulator is launched in debug mode after you add the inspection middleware in your bot's code.

  5. Now you can send messages in the chat box of your first Emulator and inspect the messages in the debugging Emulator. To inspect the state of the messages click Bot State in the debugging Emulator and unfold values on the right JSON window. You will see the state of your bot in the debugging Emulator:

    bot state

Inspect the state of a bot configured in Azure

If you want to inspect the state of your bot configured in Azure and connected to channels (like Teams) you will need to install and run ngrok.

Run ngrok

At this point you have updated your Emulator to the latest version and added the inspection middleware in your bot's code. The next step is to run ngrok and configure your local bot to your Azure Bot Channels Registration. Before running ngrok you need to run your bot locally.

To run your bot locally do the following:

  1. Navigate to your bot's folder in a terminal and set your npm registration to use the latest builds

  2. Run your bot locally. You will see your bot expose a port number like 3978.

  3. Open another command prompt and navigate to your bot's project folder. Run the following command:

    ngrok http 3978
    
  4. ngrok is now connected to your locally running bot. Copy the public IP address.

    ngrok-success

Update channel registrations for your bot

Now that your local bot is connected to ngrok you can configure your local bot to your Bot Channels Registration in Azure.

  1. Go to your Bot Channels Registration in Azure. Click Settings on the left menu and set the Messaging endpoint with your ngrok IP. If necessary add /api/messages after the IP address. (For example, https://e58549b6.ngrok.io/api/messages). Check Enable Streaming Endpoint and Save.

    endpoint

    Tip

    If Save is not enabled, you can uncheck Enable Streaming Endpoint and click Save, then check Enable Streaming Endpoint and click Save again. You need to make sure that Enable Streaming Endpoint is checked and the configuration of the endpoint is saved.

  2. Go to your bot's resource group, click Deployment, and select your Bot Channels Registration that previously deployed successfully. Click Inputs on the left side to get the appId and appSecret. Update your bot's .env file (or appsettings.json file if you have a C# bot) with the appId and appSecret.

    get-inputs

  3. Start your Emulator, click Open Bot, and put http://localhost:3978/api/messages in the Bot URL. Fill Microsoft App ID and Microsoft App password with the same appId and appSecret you added to our bot's .env (appsettings.json) file. Then click Connect.

  4. Your running bot is now connected to your Bot Channels Registration in Azure. You can test the web chat by clicking Test in Web Chat and sending messages in the chat box.

    test-web-chat

  5. Now let's enable the debugging mode in the Emulator. In your Emulator select Debug > Start Debugging. Enter the ngrok IP address (don't forget to add /api/messages) for the Bot URL (for example, https://e58549b6.ngrok.io/api/messages). For Microsoft App ID, enter your bot's app ID. For Microsoft App password, enter your bot's app secret. Make sure Open in debug mode is checked as well. Click Connect.

  6. When the debugging mode is enabled a UUID will be generated in your Emulator. A UUID is a unique ID generated every time you start the debugging mode in your Emulator. Copy and paste the UUID to the Test in Web Chat chat box or your channel's chat box. You will see the message "Attached to session, all traffic is being replicated for inspection" in the chat box.

You can start debugging your bot by sending messages in the configured channel's chat box. Your local Emulator will automatically update the messages with all the details for debugging. To inspect your bot's state of messages click Bot State and unfold the values in the right JSON window.

debug-inspection-middleware

Additional resources