VoiceXML 2.1 Development Guide Home  |  Frameset Home

  tutorial Dynamic Grammars  |  TOC  |  tutorial Screen Scraping   

Tutorial: Event Logging

Once we have written our Magnum Opus of a VoiceXML application, we might just want to keep an eye on how it performs. If your hosting webserver goes down due to an earthquake, it sure would help a lot to know exactly when your end users started to get busy signals. Or, lets say we want to accurately bill our callers for a VoiceXML conference? But maybe we just want to log the caller utterances and the grammar confidence scores just so we can tweak and improve recognition accuracy. Ladies and Germs, we will now bid a fond farewell to Uncertainty and Doubt, and will learn to create a catcher script that will save all our valuable application data to our own servers, as it happens. What could possibly be cooler?

Note: This Tutorial in server side languages assumes that you have ColdFusion Server running, and have a basic idea of CF syntax. This Tutorial also assumes that you aren't using the free Voxeo webhosting; as mentioned previously, our free application hosting has no support for ColdFusion, ASP, .NET, JSP, LAMP, or anything similar.

Step 1: Event Picking and Grinning

By now, we all know what a basic Helloworld app with a voice reco field looks like, so we won't bore you with another play-by-play. But it does bear mentioning that since we are using a VoiceXML 2.1 element, that you need to specify this within your opening <vxml> tag.  Now that we have that out of the way, we will now bore you with a listing of the errors that we will want to catch in our code. From looking in the Voxeo VoiceXML Development Guide, we can see the listing of shorthand exceptions, such as 'nomatch' that we might want to log, and probably all the fatal errors such as a 'badfetch' that are worth saving. Let's insert them as catchable events at the application scope of our code. And since we are writing a ColdFusion application, it would probably make sense to add in our CF-specific caching headers as well:


<?xml version="1.0" encoding="UTF-8"?>
<vxml version = "2.1">

<cfheader name="Cache-Control" value= "no-cache">
<cfheader name="Expires" value="#Now()#">


<meta name="maintainer" content="youremail@here.com"/>

<property name="universals" value="help"/>

<var name="MyEvent"/>

<!-- ******************** -->
<!-- START EVENT HANDLERS -->


<catch event="help nomatch noinput error connection.disconnect.hangup">
<assign name="MyEvent" expr="_event"/>
<log expr="'***** TRAPPED A' + _event +'EVENT *****'"/>
</catch>


<!-- END EVENT HANDLERS -->
<!-- ****************** -->

<form id="Form1">

</form>
</vxml>


Here we have roughly grouped all our exceptions under one single catch element. Note the usage of the 'error' event, which will catch any and all errors that have this prefix. So, any of the following events will kick off this catch handler:

The next thing you may notice is the usage of the <log> element:


<log expr="'***** TRAPPED A' + _event +'EVENT *****'"/>


This usage of the '_event' shadow variable will allow us to output the exact error in the real time debugger stream. So if the app throws us a 'error.badfetch.500' event, we will see this in the debugger:


  ***** TRAPPED A error.badfetch.500 EVENT *****



While kinda nifty, this is just the start of things. Notice also that we use the '_event' shadow variable to further nail down exactly which error was caught in the application, and we added an empty variable declaration,  at the application scope, and immediately assigned the '_event' shadow variable to this variable name upon receiving an exception:


<var name="MyEvent"/>
....
<assign name="MyEvent" expr="_event"/>



Also note that since we are using VoiceXML 2.1, we have to specifically enable the 'help' universal grammar by way of the <property> element, as this grammar is not enabled by default. Now that we have our error handling in place, let's flesh out the rest of our code...


Step 2: Catching User Input and Data

Now, we will want to figure out what info from a voice recognition field we want to log, and how we go about grabbing it from the application. If we weren't sleeping in class during our Shadow Variable Lesson, then we know the answer already. If you were sleeping during this lesson, then go straight to the Principal's office while we call your Mom and Dad to pick up up from school. And you just know Dad is going to be ticked off.

What we are going to do, is to add a few more empty variable declarations at the application scope to save the values of the <field> results that we wish to keep. Since we are logging this data in hopes of tuning our dialogs and grammars, let's assume that we want to get the values for grammar confidence scores, user utterances, and grammar return values.

You'll also note the addition of the 'MySessionID' variable. For production applications, logging the Session ID of your calls is monumentally important, as it gives you, (and us!), a way to find a particular call in our logs should you experience any failures at three a.m. when no one is watching your logger output. For this reason, it is strongly recommended that you always log the session ID for deployed Production applications.

While we are at it, let's further assume that we might want to swipe the information for a caller's recording as well. So, we will also want to add a few other empty variables for filename, filesize, and duration.

Obviously, this is just the very tip of the proverbial iceberg as far as what data we can grab from successful recognition and recording inputs. If we want to, we can get every bit of info contained within the 'application.lastResult$' shadow variable, as noted in our previous Lessons.

So now, we add our voice recognition fields, and record field into the code, and see what it looks like when it comes out of the oven:



<?xml version="1.0" encoding="UTF-8"?>
<vxml version = "2.1">

<cfheader name="Cache-Control" value= "no-cache">
<cfheader name="Expires" value="#Now()#">

<meta name="maintainer" content="youremail@here.com"/>

<property name="universals" value="help"/>

<var name="Current_Location"/>
<var name="MyEvent"/>
<var name="MyException"/>
<var name="MyConfidence"/>
<var name="MyUtterance"/>
<var name="MyInterp"/>
<var name="MySessionID" expr="session.id"/>
<var name="MySize"/>
<var name="MyDur"/>
<var name="MyTermChar"/>

<!-- ******************** -->
<!-- START EVENT HANDLERS -->


<catch event="help nomatch noinput error connection.disconnect.hangup logresults">
  <assign name="MyEvent" expr="_event"/>
  <log expr="'***** TRAPPED A' + _event +'EVENT *****'"/>
</catch>


<!-- END EVENT HANDLERS -->
<!-- ****************** -->

<form id="Form1">
  <block>

  <prompt>
    Lets step through our app and, log some results to our text file.
    For fun, we can give the app a few no inputs and no matches, just to log the events.
  </prompt>
  <goto next="#Form2"/>
  </block>
</form>

<form id="Form2">

  <field name="F_1">
      <grammar xml:lang="en-us" root="TOPLEVEL" mode="voice">
          <rule id="TOPLEVEL" scope="public">
                <one-of>
                    <item>nick</item>
                    <item>ay jay</item>
                    <item>brian</item>
                    <item>kevin</item>
                    <item>howie</item>
                </one-of>
          </rule>
    </grammar>

  <prompt>
    who is your favorite back street boy?
    is it nick, brian, ayy jay, kevin, or howie?
  </prompt>

  <filled>

    <assign name="MyConfidence" expr="F_1$.confidence"/>
    <assign name="MyInterp" expr="F_1"/>
    <assign name="MyUtterance" expr="F_1$.utterance"/>

    <prompt>
      Oh yes, <value expr="F_1"/> sure is dreamy. for a cast rat eye, that is.
    </prompt>

  </filled>
  </field>


  <field name="F_2">
      <grammar xml:lang="en-us" root="TOPLEVEL" mode="voice">
          <rule id="TOPLEVEL" scope="public">
              <one-of>
                    <item>river dancing</item>
                    <item>break dancing</item>
                    <item>white <item repeat="0-1">boy</item> dancing
                        <tag>out.F_2 = "White Folks Dancing"</tag>
                    </item>
              </one-of>
          </rule>
    </grammar>

  <prompt>
    Which is the absolute stupidest form of dancing known to man?
    Is it river dancing, break dancing, or just any kind of white boy dancing?
  </prompt>

<!--
      See Also:
      The Achy-Breaky Stomp, The Electric Slide, The Polka, The Cabbage Patch, any form of Disco, etc.

  -->

  <filled>
    <prompt>
    How right you are, <value expr="F_2"/> is quite goofy looking.
    </prompt>

    <assign name="MyConfidence" expr="F_2$.confidence"/>
    <assign name="MyInterp" expr="F_2"/>
    <assign name="MyUtterance" expr="F_2$.utterance"/>

  </filled>
  </field>

  <record name="R_1" beep="true" maxtime="10s">
  <prompt>
    You now have ten seconds to expound on your views in regards to <value expr="F_2"/>
    looking unimaginably dorky.
  </prompt>

  <filled>

    <assign name="MyDur" expr="R_1$.duration"/>
    <assign name="MySize" expr="R_1$.size"/>
    <assign name="MyTermChar" expr="R_1$.termchar"/>

    <prompt>
      Wow, thats quite a manifesto. Kazzinski would be proud.
    </prompt>

  </filled>
</record>

</form>
</vxml>




Step 3: Sending & Storing Our Results

Now that we have the catch handlers in place, and our basic code structure laid out, we have but a few additions that we still need to add to the mix. First, we are going to want to add another empty variable at the application level of our code that will hold the value of the caller's current location in the document:


<var name="Current_Location"/>


Then, we are going to re-assign this variable's value when we enter a new <form>, this way, the value will always indicate the <form> where an event or exception happened:


<form id="Form1">
  <block>
  <assign name="Current_Location" expr="'ApplicationLog.cfm#Form1'"/>
    ....


Lastly, we will want to add use the VoiceXML 2.1 <data> element to send our event logs off to our catcher script, (which we will cover in the next Step of the Lesson). The <data> element is generally used for gathering xml-formatted data from an external source, but it also works very much like the more familiar <submit> element, in that we can also use it to 'blindly' submit application data. To explain, when using the <data> element, we can send off a series of variable-value pairs to an external file, but, unlike the <submit> element, invoking this tag does not transfer the caller to the destination specified in the 'src' attribute. Using this tag will simply send our variable value pair to the specified URL, and the caller will remain within the current execution context.



<data name="MyData"
          src="Catcher.cfm"
          namelist="MyEvent Current_Location MyUtterance MyInterp MyConfidence MySessionID"
          method="get"/>




We will now add the <data> element to each of our <catch> handlers, as well as to each <filled> statement within our existing code, and while we are at it, let's also code some conditional statements into those catch handlers so that we can plan for an appropriate response to our callers if such events do crop up. And just for kicks, let's add an additional <form> that will throw us an 'unsupported.language' error , (non-fatal), and then a 'badfetch/semantic' error, (fatal), just so that we can see a 'true' error be caught and logged. We will also add a few <goto> statements within our code so that we have an intelligent call flow. Let's now take a gander at what our New and Improved VoiceXML code looks like:


<?xml version="1.0" encoding="UTF-8"?>
<vxml version = "2.1">

<cfheader name="Cache-Control" value= "no-cache">
<cfheader name="Expires" value="#Now()#">

<meta name="maintainer" content="youremail@here.com"/>


<property name="universals" value="help"/>

<var name="Current_Location"/>
<var name="MyEvent"/>
<var name="MyException"/>
<var name="MyConfidence"/>
<var name="MyUtterance"/>
<var name="MyInterp"/>
<var name="MySessionID" expr="session.id"/>
<var name="MySize"/>
<var name="MyDur"/>
<var name="MyTermChar"/>

<!-- ******************** -->
<!-- START EVENT HANDLERS -->


<catch event="help nomatch noinput error connection.disconnect.hangup logresults">
  <assign name="MyEvent" expr="_event"/>
  <log expr="'***** TRAPPED A' + _event +'EVENT *****'"/>
  <if cond="MyEvent =='help'">
    <prompt>
      God helps children, God helps elves.
      God helps those who help themselves.
    </prompt>

  <elseif cond="MyEvent == 'nomatch'"/>
    <prompt>
      Sorry, but that isnt a valid choice.
    </prompt>

  <elseif cond="MyEvent == 'noinput'"/>
    <prompt>
      Hey buddy, speak up.
      I didnt hear you say anything at all.
  </prompt>

  <elseif cond="MyEvent == 'error.unsupported.language'"/>
    <prompt>
      Yes, the Klingon language is strictly for dorks.
    </prompt>
  <goto next="ApplicationLog.cfm#Form4"/>

  <elseif cond="MyEvent == 'error.badfetch'"/>
    <data name="MyDataName" src="Catcher.cfm" namelist="MyEvent Current_Location MySessionID" method="get"/>
  <exit/>

  <elseif cond="MyEvent == 'error.semantic'"/>
    <data name="MyDataName" src="Catcher.cfm" namelist="MyEvent Current_Location MySessionID" method="get"/>
  <exit/>

  <elseif cond="MyEvent == 'disconnect.hangup'"/>
    <data name="MyDataName" src="Catcher.cfm" namelist="MyEvent Current_Location MySessionID" method="get"/>
  <exit/>

  <else/>
    <log expr="'***** AN ERROR WITHOUT A CATCH HANDLER HAS OCCURED: ' + _error +' *****'"/>
  </if>

  <data name="MyDataName" src="Catcher.cfm" namelist="MyEvent Current_Location MySessionID" method="get"/>
  <goto expr="Current_Location"/>

</catch>

<!-- END EVENT HANDLERS -->
<!-- ****************** -->

<form id="Form1">
  <block>
    <assign name="Current_Location" expr="'ApplicationLog.cfm#Form1'"/>

    <prompt>
      Lets step through our app and, log some results to our text file.
      For fun, we can give the app a few no inputs and no matches, just to log the events.
    </prompt>
    <goto next="#Form2"/>
  </block>
</form>

<form id="Form2">
  <block>
    <assign name="Current_Location" expr="'ApplicationLog.cfm#Form2'"/>
  </block>

  <field name="F_1">
      <grammar xml:lang="en-us" root="TOPLEVEL" mode="voice">
          <rule id="TOPLEVEL" scope="public">
                <one-of>
                    <item>nick</item>
                    <item>ay jay</item>
                    <item>brian</item>
                    <item>kevin</item>
                    <item>howie</item>
                </one-of>
          </rule>
    </grammar>

  <prompt>
    Who is your favorite back street boy?
    Is it nick, brian, ayy jay, kevin, or howie?
  </prompt>

  <filled>
    <assign name="MyConfidence" expr="F_1$.confidence"/>
    <assign name="MyInterp" expr="F_1"/>
    <assign name="MyUtterance" expr="F_1$.utterance"/>

    <prompt>
    Oh yes, <value expr="F_1"/> sure is dreamy. for a cast rat eye, that is.
    </prompt>

    <data name="MyDataName" src="Catcher.cfm" namelist="MyEvent Current_Location MyUtterance MyInterp
                    MyConfidence MySessionID" method="get"/>


  </filled>
  </field>

  <field name="F_2">
      <grammar xml:lang="en-us" root="TOPLEVEL" mode="voice">
          <rule id="TOPLEVEL" scope="public">
              <one-of>
                    <item>river dancing</item>
                    <item>break dancing</item>
                    <item>white <item repeat="0-1">boy</item> dancing
                        <tag>out.F_2 = "White Folks Dancing"</tag>
                    </item>
              </one-of>
          </rule>
    </grammar>

  <prompt>
    Which is the absolute stupidest form of dancing known to man?
    Is it river dancing, break dancing, or just any kind of white boy dancing?
  </prompt>

<!--
      See Also:
      The Achy-Breaky Stomp, The Electric Slide, The Polka, The Cabbage Patch, Disco, etc.
  -->

  <filled>
    <prompt>
      How right you are, <value expr="F_2"/> is quite goofy looking.
    </prompt>

    <assign name="MyConfidence" expr="F_2$.confidence"/>
    <assign name="MyInterp" expr="F_2"/>
    <assign name="MyUtterance" expr="F_2$.utterance"/>

  <data name="MyDataName" src="Catcher.cfm" namelist="MyEvent Current_Location MyUtterance
                    MyInterp MyConfidence MySessionID" method="get"/>


  </filled>
  </field>

<record name="R_1" beep="true" maxtime="10s">
  <prompt>
    You now have ten seconds to expound on your views in regards to <value expr="F_2"/>
    looking unimaginably dorky.
  </prompt>

  <filled>
    <assign name="MyDur" expr="R_1$.duration"/>
    <assign name="MySize" expr="R_1$.size"/>
    <assign name="MyTermChar" expr="R_1$.termchar"/>

    <prompt>
    Wow, thats quite a manifesto. Kazzinski would be proud.
    </prompt>

    <data name="MyDataName" src="Catcher.cfm" namelist="MyEvent Current_Location MyDur MySize
                  MyTermChar MySessionID" method="get"/>


  <goto next="#Form3"/>
  </filled>
</record>

</form>

<form id="Form3">
  <block>
    <assign name="Current_Location" expr="'ApplicationLog.cfm#Form3'"/>

    <prompt>
    Just for kicks, we will throw the app an unsupported Klingon language error to log.
    </prompt>
    <prompt xml:lang="Klingon">
    Klaatu Barrada Nictu!
    </prompt>

  </block>
</form>

<form id="Form4">
  <block>
    <assign name="Current_Location" expr="'ApplicationLog.cfm#Form4'"/>

    <prompt>
      The last thing we will do is to transition to a bogus page,
      and log the error result to our text file.
    </prompt>

    <goto next="Bogus.cfm"/>

  </block>
</form>
</vxml>



A few additional words of explanation. Following an error./disconnect/badfetch/semantic, it's always a good idea to explicitly <exit/>, so we can avoid any kind of post-hangup application 'looping'. Since we are dealing with a pretty static piece of VXML code, we also added some navigational directions within our catch handlers that will allow us to sequentially navigate through every event we have coded in the application. This is why some <catch> events have explicit <goto> statements written for them, and others use the 'generic' <goto expr="Current_Location"/> directive at the very bottom of our catch handler.


Step 4: Adding Our Catcher Script

Now that we have our VoiceXML document completed, all that remains is to whip out a simple ColdFusion document that will take the querystring values from our <data> element, and write it to a text file for safekeeping. The code presented below assumes that we will have a subdirectory named 'txt' that resides in the same location of our code, so before running the script for the first time, be sure that you have indeed created this directory.

But, since we are going to be using local CF variables in our text-writing file, we will first need to define these variables in an 'Application.cfm' file. This document serves as the root document for all CF pages in a particular application; of special note is the fact that the filename is *extremely* case sensitive...an 'application.cfm' file simply will not work at all!


<CFSILENT>
<CFPARAM NAME="url.MyEvent" default=""/>
<CFPARAM NAME="url.MyException" default=""/>
<CFPARAM NAME="url.CurrentLocation" default=""/>
<CFPARAM NAME="url.MyUtterance" default=""/>
<CFPARAM NAME="url.MyConfidence" default=""/>
<CFPARAM NAME="url.MyInterp" default=""/>
<CFPARAM NAME="url.MySessionID" default=""/>

<CFPARAM NAME="url.MyDur" default=""/>
<CFPARAM NAME="url.MySize" default=""/>
<CFPARAM NAME="url.MyTermChar" default=""/>
</CFSILENT>


All the varaibles that are sent to our catcher script will be via a submit method=GET, so we need to assign the empty variables with the 'url' prefix, as indicated above. In addition, we also want to be sure that our CF doesnt output any additional whitespace, so we will surround all the content of Application.cfm within <CFSILENT> tags.

Since we are obviosly going to be outputting a goodly chunk of CF code within this document, the first thing we will want to do is to start off with some <CFOUTPUT> tags. Since we will also want to log the time and date that events occured, we should then use the #dateformat# and #timeformat# functions within these tags, and set up how we want these values to be formatted:


  <CFOUTPUT>
    <CFSET MyDate="#dateformat(now(),"mm-dd-yyyy")# - #timeformat(now(),"HH:mm:ss")#"/>
  </CFOUTPUT>


The above scheme will give us a date and time output in the following format:

  02-14-2003 - 21:21:17


Now comes the juicy part. We will next use the <CFFILE> tag to effectively write the CF querystring values to our text file:


  <CFOUTPUT>
    <CFSET MyDate="#dateformat(now(),"mm-dd-yyyy")# - #timeformat(now(),"HH:mm:ss")#"/>
  <CFFILE
  action="APPEND"
  file="c:\MyDirectory/MyFile.txt"
  output=""/>

  </CFOUTPUT>


We will want to make certain that we specify the 'action' attribute to 'append' in order to keep our application from overwriting previously captured data. The 'file' attribute must point to the local hosting machine's directory where the code is stored, else the results will get written to Limbo.


The very last thing we will need to do is to define just what will be written to text when this ColdFusion script gets hit. Since we already assigned our VoiceXML variables to local variables in our Application.cfm file, we simply list those variables over again in a tidy little format within the 'output' attribute of the <CFFILE> tag. ALso note that since we are using the <data> tag, our target page must be a plain-vanilla XML document, hence the <xml> declaration at the very top:


<?xml version="1.0" ?>

<cfheader name="Cache-Control" value= "no-cache">
<cfheader name="Expires" value="#Now()#">

<CFOUTPUT>

  <CFSET MyDate="#dateformat(now(),"mm-dd-yyyy")# - #timeformat(now(),"HH:mm:ss")#"/>
  <CFFILE action="APPEND"
  file="c:\MyDirectory/MyFile.txt"
  output="
=== APPLICATION EVENT LOG ===
Date:            #MyDate#
Event:          #url.MyEvent#
Code Location:  #url.Current_Location#
Grammar Utterance:  #url.MyUtterance#
Grammar Interpretation: #url.MyInterp#
Grammar Confidence: #url.MyConfidence#
SessionID: #url.MySessionID#
Recording Size: #url.MySize#
Recording Duration: #url.MyDur#
Recording TermChar: #url.MyTermChar#
"/>
</CFOUTPUT>


</xml>




Step 5: upload, and try it out

Make sure to save the required files to the same directory as your script, and then give it a ring! After stepping through the application, check the file in your 'txt' directory, and you should see something like this:


=== APPLICATION EVENT LOG ===
Date:            02-18-2003 - 23:55:01
Event:          undefined
Code Location:  ErrorLog.cfm#Form2
Grammar Utterance:  howee
Grammar Interpretation: Howie
Grammar Confidence: 0.7
SessionID: abc123...
Recording Size:
Recording Duration:
Recording TermChar:


Download the Code!

  Source Code

What was just covered:




  ANNOTATIONS: EXISTING POSTS
agonzalez
5/19/2006 7:07 AM (EDT)
in the motorola source there are two errors log is placed not in the txt folder so in the same folder as the catcher and the xml tags are bad placed thank you
MattHenry
5/19/2006 11:02 AM (EDT)


Hello Angel,

I'm really sorry, but I don't know what it is that you are talking about here; your statement seems to be out of context for the concepts covered in the tutorial.

If you can provide specific details as to what is 'wrong', I would be happy to take a look.....


~Matthew Henry
dpinter
8/3/2009 10:06 PM (EDT)
How can I view the call logs for errors, what's happening with each placed call, etc.? I saw some options under account, but it says I need to pay a fee to use. Is there another way?
jdyer
8/3/2009 10:20 PM (EDT)
Hello,

Generally during development we recommend you use the application debugger. The application debugger is the best diagnostic tool that we have for getting insight into applications that aren't behaving the way we want them to, but it takes a bit of experience to intuit the messages displayed.

Click on the "account" tab at the top right of evolution site, and select the "application debugger" prior to making test calls to capture the data. You can then select the "support" tab in the debugger to send us a problem report. Some additional links on this utility are provided below:

http://docs.voxeo.com/voicexml/2.0/loggerfeatures.htm
http://docs.voxeo.com/voicexml/2.0/mot_loggermessages.htm
http://docs.voxeo.com/voicexml/2.0/gettingsupport.htm

I do hope this helps, and if there are any other questions please let us know!  Our team is always standing by to provide any help our developers may require!

Regards,

John Dyer
Customer Engineer
Voxeo Support
MattHenry
8/3/2009 10:20 PM (EDT)


Hi Dan,

I'd be curious to see where you saw that payment was required to access call logs: If you happen to remember the URL and the  general area where this was printed, I can probably help explain. I think what might be helpful to you is under "ACCOUNT" --> "Application Debugger", which is the most widely used utility to assist with application development. You might also find these links useful in getting acclimated to its features:

http://www.vxml.org/loggerfeatures.htm
http://www.vxml.org/mot_loggermessages.htm
http://www.vxml.org/gettingsupport.htm

Cheers!

~Matthew Henry

plowden
2/8/2011 9:59 AM (EST)
I am dissapointed, your Klingons link is a broken link.
I feel I am going to fail VoiceXML now.
Sad regards,
Paul
voxeojeff
2/9/2011 9:03 AM (EST)
Hi Paul,

Don't let that broken link deter you. :-)

I went ahead and fixed it behind the scenes, and it will update once the next documentation build is deployed.

Best regards,
Jeff Menkel
Voxeo Support

login
  tutorial Dynamic Grammars  |  TOC  |  tutorial Screen Scraping   

© 2013 Voxeo Corporation  |  Voxeo IVR  |  VoiceXML & CCXML IVR Developer Site