Search code examples
node.jsdebuggingloggingbotframeworkmiddleware

Debugging transcript logging middleware in node.js & Bot framework V4.4


I've just added my first bit of middleware to my chatbot for transcript logging by following this stack overflow answer here.

However, the implementation is throwing up a few errors, such as:

[onTurnError]: TypeError: this.logger.log is not a function
[onTurnError]: TypeError: Cannot read property 'role' of undefined
BotFrameworkAdapter.processActivity(): 500 ERROR - Error: BotFrameworkAdapter.sendActivity(): missing conversation id.

These errors are thrown straight after I initialise the conversation and the user sends the first "GET_STARTED" message.

The transcript logging is working fine with a transcript of every (short!) conversation being posted to my Azure Blob storage.

However, as it's the first time I've used middleware I'm unsure how to debug these errors and get the chatbot working as intended.

The onTurn function for transcript logging is pretty much exactly like the linked example, I've just added an await next(); at the end:

this.onTurn(async (turnContext, next) => {
// See https://aka.ms/about-bot-activity-message to learn more about the message and other activity types.
if (turnContext.activity.type === ActivityTypes.Message) {
   if (turnContext.activity.text === '!history') {
      // Retrieve the activities from the Transcript (blob store) and send them over to the channel when a request to upload history arrives. This could be an event or a special message activity as above.
      // Create the connector client to send transcript activities
      let connector = turnContext.adapter.createConnectorClient(turnContext.activity.serviceUrl);

      // Get all the message type activities from the Transcript.
      let continuationToken = '';
      var count = 0;

      // WebChat and Emulator require modifying the activity.Id to display the same activity again within the same chat window
      let updateActivities = [ 'webchat', 'emulator', 'directline' ].includes(turnContext.activity.channelId);
      let incrementId = 0;
      if (updateActivities && turnContext.activity.id.includes('|')) {
                    incrementId = parseInt(turnContext.activity.id.split('|')[1]) || 0;
      } do {
      // Transcript activities are retrieved in pages.  When the continuationToken is null, there are no more activities to be retrieved for this conversation.
      var pagedTranscript = await this.transcriptStore.getTranscriptActivities(turnContext.activity.channelId, turnContext.activity.conversation.id, continuationToken);
      let activities = pagedTranscript.items.filter(item => item.type === ActivityTypes.Message);

         if (updateActivities) {
            activities.forEach(function(a) {
            incrementId++;
            a.id = `${ turnContext.activity.conversation.id }|${ incrementId.toString().padStart(7, '0') }`;
            a.timestamp = new Date().toISOString();
            a.channelData = []; // WebChat uses ChannelData for id comparisons, so we clear it here
            a.replyToId = '';
          });
         }

         // DirectLine only allows the upload of at most 500 activities at a time. The limit of 1500 below is arbitrary and up to the Bot author to decide.
         count += activities.length;
         if (activities.length > 500 || count > 1500) {
            throw new InvalidOperationException('Attempt to upload too many activities');
         }

         await connector.conversations.sendConversationHistory(turnContext.activity.conversation.id, { activities });
                    continuationToken = pagedTranscript.continuationToken;
         }
         while (continuationToken != null);

         console.log("Transcript sent");
         } else {
            // Echo back to the user whatever they typed.
            console.log(`User sent: ${ turnContext.activity.text }\n`);
         }
         } else if (turnContext.activity.type === ActivityTypes.ConversationUpdate) {
            // Send greeting when users are added to the conversation.
            console.log('Welcome Message');
         } else {
            // Generic message for all other activities
            console.log(`[${ turnContext.activity.type } event detected]`);
         }
     await next();
 });

Below is also my onMessage function:

this.onMessage(async (context, next) => {
     await sendTyping(context);
     this.logger.log('Processing a Message Activity');
     // await logMessageText(storageBlob, context);

     const qnaResults = await this.qnaMaker.getAnswers(context);

     // Show choices if the Facebook Payload from ChannelData is not handled
     if (!await fbBot.processFacebookPayload(context, context.activity.channelData)) {
         // detect if Facebook Messenger is the channel being used
         if (context.activity.channelId === 'facebook') {
             let facebookPayload = context.activity.channelData;
             let fbSender = facebookPayload.sender;
             var fbPSID = fbSender.id;
             if (qnaResults[0]) {
                 const { answer, context: { prompts } } = qnaResults[0];

                 // set a variable for the prompts array from QnA
                 var qnaPrompts = null;
                 if (qnaResults[0].context != null) {
                     qnaPrompts = qnaResults[0].context.prompts;
                 }

                 // map the prompts to the required format for quick_replies channelData
                 var qnaPromptsArray = qnaPrompts.map(obj => {
                     return { content_type: 'text', title: obj.displayText, payload: obj.displayText };
                 });

                 let reply;
                 if (prompts.length) {
                     const quickReply = {
                         channelData: {
                         text: answer,
                         quick_replies: qnaPromptsArray
                         }
                     };
                     reply = quickReply;
                 } else {
                     reply = {
                         channelData: {
                         text: answer
                         }
                     };
                 }

                 await context.sendActivity(reply);

             // If no answers were returned from QnA Maker, reply with help.
             } else {
                 let errorCardFB = await fbCards.getErrorCardFB(fbPSID);
                 await context.sendActivity(errorCardFB);
             }
         } else {
         // If an answer was received from QnA Maker, send the answer back to the user.
             if (qnaResults[0]) {
                 const { answer, context: { prompts } } = qnaResults[0];

                 let reply;
                 if (prompts.length) {
                     const card = {
                         'type': 'AdaptiveCard',
                         'body': [
                             {
                             'type': 'TextBlock',
                             'text': answer,
                              wrap: true
                             }
                         ],
                         'actions': prompts.map(({ displayText }) => ({ type: 'Action.Submit', title: displayText, data: displayText })),
                         '$schema': 'http://adaptivecards.io/schemas/adaptive-card.json',
                         'version': '1.1'
                     };

                 reply = { attachments: [CardFactory.adaptiveCard(card)] };
                 } else {
                     reply = answer;
                 }

                 await context.sendActivity(reply);

             // If no answers were returned from QnA Maker, reply with help.
             } else {
                 await context.sendActivity('I\'m sorry, I don\'t have an answer for that. Please ask me something else, such as: \n\n "What Is Mental Health?" \n\n "What Is NeuroDiversity" \n\n "Help"');
             }
         }
     }

     // By calling next() you ensure that the next BotHandler is run.
     await next();
 });

As I'm unsure how to debug middleware and as I'm using Facebook Messenger as my Chatbot channel I'm not sure how to work out what is causing these errors.

Can anyone point me in the right direction?


Solution

  • If you are intending for the transcript logger to be used as middleware, then it needs to be implemented in the index.js file. Middleware is attached to your adapter and doesn't live in the "dialog" code.

    Your code should look something like this:

    const {
      [...],
      ConsoleTranscriptLogger,
      TranscriptLoggerMiddleware,
      [...]
    } = require( 'botbuilder' );
    
    // The bot.
    const { WelcomeBot } = require( './bots/welcomeBot' );
    const { MainDialog } = require( './dialogs/mainDialog' );
    
    const ENV_FILE = path.join( __dirname, '.env' );
    require( 'dotenv' ).config( { path: ENV_FILE } );
    
    // Create HTTP server
    let server = restify.createServer();
    server.listen( process.env.port || process.env.PORT || 3978, function () {
      console.log( `\n${ server.name } listening to ${ server.url }` );
    } );
    
    // configure middleware
    const logstore = new ConsoleTranscriptLogger();
    const logActivity = new TranscriptLoggerMiddleware( logstore );
    [...other middleware...]
    
    // Create adapter.
    const adapter = new BotFrameworkAdapter( {
      appId: process.env.MicrosoftAppId,
      appPassword: process.env.MicrosoftAppPassword
    } )
      .use( logActivity );
    
    [...other bot code...]
    

    At this point, you should be good to go. The logger will parse every message that passes thru the adapter and render it in the console.

    You can read more about middleware here.

    Hope of help!