This chapter is designed to show you some fundamental dialplan usage concepts that we use in nearly every dialplan. We’ve developed these recipes to show you how we’ve found the usage of these dialplan applications to be of the greatest value and flexibility.
You need to perform basic math—such as increasing a counting variable—and do it using a conditional statement.
In many cases you will need to perform basic math, such as when incrementing the counter variable when performing loops. To increase the counter variable we have a couple of methods which are common. First, we can use the standard conditional matching format in Asterisk:
[CounterIncrement] exten => start,1,Verbose(2,Increment the counter variable) ; Set the initial value of the variable same => n,Set(CounterVariable=1) same => n,Verbose(2,Current value of CounterVariable is: ${CounterVariable}) ; Now we can increment the value of CounterVariable same => n,Set(CounterVariable=$[${CounterVariable} + 1]) same => n,Verbose(2,Our new value of CounterVariable is: ${CounterVariable}) same => n,Hangup()
Alternatively, in versions of Asterisk greater than and including
Asterisk 1.8, we can use the INC()
dialplan function:
[CounterIncrement] exten => start,1,Verbose(2,Increment the counter variable) ; Set the inital value of the variable same => n,Set(CounterVariable=1) same => n,Verbose(2,Current value of CounterVariable is: ${CounterVariable}) ; Now we can increment the value of CounterVariable same => n,Set(CounterVariable=${INC(CounterVariable)}) same => n,Verbose(2,Our new value of CounterVariable is: ${CounterVariable}) same => n,Hangup()
Additionally, we can use the IF()
function to determine whether we should
be incrementing the value at all:
[CounterIncrement] exten => start,1,Verbose(2,Increment the counter variable) ; Set the inital value of the variable same => n,Set(CounterVariable=1) same => n,Verbose(2,Current value of CounterVariable is: ${CounterVariable}) ; Here we use the RAND() to randomly help us determine whether we should increment ; the CounterVariable. We've set a range of 0 through 1, which we'll use ; as false (0) or true (1) same => n,Set(IncrementValue=${RAND(0,1)}) ; Now we can increment the value of CounterVariable if IncrementValue returns 1 same => n,Set(CounterVariable=${IF($[${IncrementValue} = 1]? ${INC(CounterVariable)}:${CounterVariable}) same => n,Verbose(2,Our IncrementValue returned: ${IncrementValue}) same => n,Verbose(2,Our new value of CounterVariable is: ${CounterVariable}) same => n,Hangup()
The incrementing of variables in Asterisk is one of the more common functionalities you’ll encounter, especially as you start building more complex dialplans where you need to iterate over several values. In versions of Asterisk prior to 1.8, the most common method for incrementing (and decrementing) the value of a counter variable was with the use of the dialplan conditional matching format, which we explored in the first example.
With newer versions of
Asterisk, the incrementing and decrementing of variables can be done
using the INC()
and DEC()
dialplan functions respectively. The use
of the INC()
and DEC()
functions requires you to specify only
the name of the variable you want to change, and a value is then
returned. You do not specify the value to change, which means you would
provide ${INC(CounterVariable)}
, not
${INC(${CounterVariable})}
. If the
value of ${CounterVariable}
returned
5, then INC()
would try and increment
the value of the channel variable 5, and not increment the value of 5 to
the value 6.
Of course, you could pass
${MyVariableName}
to the INC()
function, and if the value of ${MyVariableName}
contained the name of the
variable you actually wanted to retrieve the value of, increment, and
then return, you could do that.
It should also be explicitly stated that INC()
and DEC()
do not modify the original value of the
variable. They simply check to see what the current value of the channel
variable is (e.g., 5), increment the value in memory, and return the new
value (e.g., 6).
In our last code block,
we’ve used the RAND()
dialplan
function to return a random 0 or 1 and assign it to the IncrementVariable
channel variable. We then
use the value stored in ${IncrementVariable}
to allow
the IF()
dialplan function to return either an
incremented value or the original value. The IF()
function has the syntax:
${IF($[...conditional
statement...]?true_return_value:false_return_value)}
The true or false return
values are optional (although you need to return something in one of
them). In our case we set our true_return_value to
${INC(CounterVariable)}
, which would
return an incremented value that would then be assigned to the
CounterVariable
channel variable. We set the
false_return_value to ${CounterVariable}
, which would return the
original value of ${CounterVariable}
to the CounterVariable
channel variable, thereby not
changing the value.
What we’ve described are common methods for incrementing (and decrementing) channel variables in the dialplan that we’ve found useful.
A basic loop which iterates a certain number of times using a counter can be created in the following manner:
[IteratingLoop] exten => start,1,Verbose(2,Looping through an action five times.) same => n,Set(X=1) same => n,Verbose(2,Starting the loop) same => n,While($[${X} <= 5]) same => n,Verbose(2,Current value of X is: ${X}) same => n,Set(X=${INC(X)}) same => n,EndWhile() same => n,Verbose(2,End of the loop) same => n,Hangup()
We could build the same
type of counter-based loop using the GotoIf()
application as well:
[IteratingLoop] exten => start,1,Verbose(2,Looping through an action five times.) same => n,Set(X=1) same => n,Verbose(2,Starting the loop) same => n(top),NoOp() same => n,Verbose(2,Current value of X is: ${X}) same => n,Set(X=${INC(X)}) same => n,GotoIf($[${X} <= 5]?top) same => n,Verbose(2,End of the loop) same => n,Hangup()
Sometimes you might have
multiple values you want to check for. A common way of iterating through
several values is by saving them to a variable and separating them with
a hyphen. We can then use the CUT()
function to select the field that we want to check against:
[LoopWithCut] exten => start,1,Verbose(2,Example of a loop using the CUT function.) same => n,Set(Fruits=Apple-Orange-Banana-Pineapple-Grapes) same => n,Set(FruitWeWant=Pineapple) same => n,Set(X=1) same => n,Set(thisFruit=${CUT(Fruits,-,${X})}) same => n,While($[${EXISTS(${thisFruit})}]) same => n,GotoIf($[${thisFruit} = ${FruitWeWant}]?GotIt,1) same => n,Set(X=${INC(X)}) same => n,thisFruit=${CUT(Fruits,-,${X})}) same => n,EndWhile() ; We got to the end of the loop without finding what we were looking for. same => n,Verbose(2,Exiting the loop without finding our fruit.) same => n,Hangup() ; If we found the fruit, then the GotoIf() will get us here. exten => GotIt,1,Verbose(2,We matched the fruit we were looking for.) same => n,Hangup()
We’ve explored three
different blocks of code which show common ways of performing loops in
the Asterisk dialplan. We’ve shown two ways of performing a
counting-based loop, which lets you iterate through a set of actions a
specified number of times. The first method uses the While()
and EndWhile()
applications to specify the bounds
of the loop, with the check happening at the top of the loop. The second
method uses the GotoIf()
application
to check whether the loop continues at the bottom of the loop
block.
The third loop we’ve shown
uses the CUT()
dialplan function to
move through fields in a list of words that we check for in our loop.
When we find what we’re looking for, we jump to another location in the
dialplan (the GotIt
extension), where
we can then continue performing actions knowing we’ve found what we’re
looking for. If we iterate through the loop enough times, the channel
variable thisFruit
will contain
nothing, and the loop will then continue at the EndWhile()
application, falling through to the
rest of the priorities below it. If we get there, we know we’ve fallen
out of our loop without finding what we’re looking for.
There are other variations
on these loops, such as with the use of the
ContinueWhile()
and ExitWhile()
applications, and the method with which we search for data can also be
different, such as with the use of the ARRAY()
and HASH()
dialplan functions, which are useful
when returning data from a relational database using
func_odbc.
When receiving calls in your auto-attendant, you sometimes need to redirect calls to a different location of the dialplan based on the date and/or time of day.
[AutoAttendant] exten => start,1,Verbose(2,Entering our auto-attedant) same => n,Answer() same => n,Playback(silence/1) ; We're closed on New Years Eve, New Years Day, Christmas Eve, and Christmas Day same => n,GotoIfTime(*,*,31,dec?holiday,1) same => n,GotoIfTime(*,*,1,jan?holiday,1) same => n,GotoIfTime(*,*,24,dec?holiday,1) same => n,GotoIfTime(*,*,25,dec?holiday,1) ; Our operational hours are Monday-Friday, 9:00am to 5:00pm. same => n,GotoIfTime(0900-1700,mon-fri,*,*?open,1:closed,1) exten => open,1,Verbose(2,We're open!) same => n,Background(custom/open-greeting) ... exten => closed,1,Verbose(2,We're closed.) same => n,Playback(custom/closed-greeting) same => n,Voicemail(general-mailbox@default,u) same => n,Hangup() exten => holiday,1,Verbose(2,We're closed for a holiday.) same => n,Playback(custom/closed-holiday) same => n,Voicemail(general-mailbox@default,u) same => n,Hangup()
We don’t just need to use
the GotoIfTime()
application in an
auto-attendant. Sometimes we want to forward calls to people based on
time, such as when IT staff is not in the office on weekends, but are on
call:
[Devices] exten => 100,1,Verbose(2,Calling IT Staff.) same => n,GotoIfTime(*,sat&sun,*,*?on_call,1) same => n,Dial(SIP/itstaff,30) same => n,Voicemail(itstaff@default,u) same => n,Hangup() exten => on_call,1,Verbose(2,Calling On-Call IT Staff.) same => n,Dial(SIP/myITSP/4165551212&SIP/myITSP/2565551212,30) same => n,Voicemail(itstaff@default,u) same => n,Hangup()
At the top of our
auto-attendant, we Answer()
the call
and Playback(silence/1)
which are
standard actions prior to playing back prompts to the caller. This
eliminates the first few milliseconds of a prompt from being cut off.
After that, we then start our checks with the GotoIfTime()
application. We start with
specific matches first (such as particular days of the week) before we
do our more general checks, such as our 9:00 a.m. to 5:00 p.m., Monday
to Friday checks. If we did it the other way around, then we’d be open
Monday–Friday, 9:00 a.m.–5:00 p.m. on holidays.
The GotoIfTime()
application contains five fields
(one of which is optional; we’ll discuss it momentarily). The fields
are: time range, days of the week, days of the month, and months. The
fifth field, which is optional and specified after the months field, is
the timezone field. If you are servicing multiple timezones, you could
use this to have different menus and groups of people answering the
phones between 9:00 a.m. and 5:00 p.m. for each timezone.
On holidays, we have a
separate part of the menu in which we play back a prompt and then send
the caller to Voicemail()
, but you
could, of course, send the caller to any functionality that you wish.
We’ve used the asterisk (*
) symbol to
indicate that at any time of the day and any day of the week, but only
on the 31st day of December, should calls go to the holiday extension.
We then specify in the same manner for the 1st of January, the 24th of
December, and the 25th of December, all of which are handled by the
holiday extension.
After we’ve determined it’s
not a holiday, then we perform our standard check to see if we should be
using the open extension—which we’ll use when the office is open—or the
closed extension—which plays back a prompt indicating we’re closed and
to leave a message in the company Voicemail()
.
In our second block of
code, we’ve also shown how you could use the GotoIfTime()
to call IT staff on the weekends
(Saturday and Sunday, any time of the day). Normally, people would call
the extension 100, and if it is Monday to Friday, they would be directed
to the SIP device registered to [itstaff]
in sip.conf. Of course, since the people reading
and implementing this probably are IT staff, there is a good chance you
will alter this to call at hours which are more sane.
[Authentication] exten => start,1,Verbose(2,Simple Authenicate application example) same => n,Playback(silence/1) same => n,Authenticate(1234) same => n,Hangup()
Here is a slightly modified
version that sets the maxdigits value to 4
, thereby
not requiring the user to press the #
key when done
entering the password:
[Authentication] exten => start,1,Verbose(2,Simple Authenicate application example) same => n,Playback(silence/1) same => n,Authenticate(1234,,4) same => n,Hangup()
By starting our password
field with a leading forward slash (/
), we can utilize an external file as the
source of the password(s):
[Authentication] exten => start,1,Verbose(2,Simple Authenicate application example) same => n,Playback(silence/1) same => n,Authenticate(/etc/asterisk/authenticate/passwd_list.txt) same => n,Hangup()
If we use the d
flag, Asterisk will interpret the path
provided as a database key in the Asterisk DB instead of a file:
[Authentication] exten => start,1,Verbose(2,Simple Authenicate application example) same => n,Playback(silence/1) same => n,Authenticate(/authenticate/password,d) same => n,Hangup()
We can insert and modify the password we’re going to use in the Asterisk database using the Asterisk CLI:
*CLI>database put authenticate/password 1234 this_does_not_matter
Updated database successfully *CLI>database show authenticate
/authenticate/password/1234 : this_does_not_matter 1 result found.
A neat modification for temporary passwords in the Asterisk
Database (AstDB) is by adding the r
flag along with the d
flag to remove
the password from the AstDB upon successful authentication:
[Authentication] exten => start,1,Verbose(2,Simple Authenicate application example) same => n,Playback(silence/1) same => n,Authenticate(/authenticate/temp_password,dr) same => n,Hangup()
After we insert the password into the database, we can use it until an authentication happens, and then it is removed from the database:
*CLI>database put authenticate/temp_password 1234 this_does_not_matter
Updated database successfully *CLI>database show authenticate
The Authenticate()
dialplan application is quite
basic at its core; a password is provided to the dialplan application,
and that password must be entered correctly to authenticate and continue
on in the dialplan. The Authenticate()
application is one of the older
applications in Asterisk, and it shows that by the number of available
options for something that should be an almost trivial application.
While much of the functionality provided by the
Authenticate()
application can be done in the
dialplan using dialplan functions and other applications, it is somewhat
nice to have much of the functionality contained within the application
directly.
We’ve provided examples of
some of this functionality in the Solution section. We started off with
a simple example of how you can provide a password to Authenticate()
and then require that password
to be entered before continuing on in the dialplan. The first example required the
user to enter a password of 1234
followed by the #
key to signal that
entering of digits was complete. Our second example shows the use of the
maxdigits field, with a value of 4
, to not require the user to press the
#
key when done entering the
password.
We went on to show how you could provide a path to the password field, which would allow you to utilize a file for authentication. Using an external file can be useful if you want to use an external script to rotate the password fairly often. One of the particular uses we can think of would be the SecurID system, by RSA, which uses cards that contain a number that rotates over a period of time to be synchronized with a centralized server that is using the same algorithm to generate passwords. If you wanted to tie this system into Asterisk, you could have a script that rotated the key on a timely basis.
Instead of using a file for
the location of the password, there is an option that lets you use the
Asterisk DB. With the d
flag, we can
tell Authenticate()
that the path
we’re providing is that of a family/key relationship in the AstDB. An
additional flag, r
, can also be
provided that removes the key from the database upon successful
authentication, which provides a method for one-time-use
passwords.
The Authenticate()
application is a general
purpose authentication mechanism which provides a base layer without
taking into consideration which user or caller is attempting to
authenticate. It would be fairly straightforward, however, to add that
layer with some additional dialplan, or you could even utilize some of
the dialplan we’ve provided in this book.
You need to provide an authentication mechanism in your dialplan, but wish to use the credentials already in place for retrieving voicemail.
[Authentication] exten => start,1,Verbose(2,Attempting to authenticate caller with voicemail creds.) same => n,Playback(silence/1) ; This is where we do our authentication same => n,VMAuthenticate(@default) same => n,Verbose(2,The caller was authenticated if we execute this line.) same => n,Goto(authenticated,1) exten => authenticated,1,Verbose(2,Perform some actions.) ; would contain 'default'. same => n,Verbose(2,Value of AUTH_CONTEXT: ${AUTH_CONTEXT}) ; mailbox '100' was authenticated. same => n,Verbose(2,Value of AUTH_MAILBOX: ${AUTH_MAILBOX}) same => n,Playback(all-your-base) same => n,Hangup()
If you wanted to explicitly define the mailbox to authenticate against, you could place the extension number to authenticate with in front of the voicemail context:
same => n,VMAuthenticate(100@default)
And if you don’t like the introductory prompts that VMAuthenticate()
plays, you could modify your
dialplan to play a different initial prompt by adding the s
flag:
same => n,Playback(extension) same => n,VMAuthenticate(@default,s)
Also, if we wanted to
provide the option of letting someone skip out of authenticating
altogether, and perhaps speak with an operator, we could provide the
a
extension in our context, which
allows the user to press *
to jump to
the a
extension:
[Authentication] exten => start,1,Verbose(2,Attempting to authenticate caller with voicemail creds.) same => n,Playback(silence/1) ; This is where we do our authentication same => n,VMAuthenticate(@default) same => n,Verbose(2,The caller was authenticated if we execute this line.) same => n,Goto(authenticated,1) exten => a,1,Verbose(2,Calling the operator.) same => n,Dial(SIP/operator,30) same => n,Voicemail(operator@default,u) same => n,Hangup()
The VMAuthenticate()
application provides us a
useful tool for authenticating callers using credentials the user
already knows, and uses often. By using the caller’s own voicemail box
number and password, it is one less piece of authentication information
to be memorized on the part of the caller. It also provides a
centralized repository of credentials the administrator of the system
can control and enforce to be secure. Since we have the ability to skip
playing back the initial prompts, we could obtain the mailbox number
used for authentication in several ways: by asking the user to provide
it using another application or a lookup from a database, or simply by
dialing it from the phone and performing a pattern match.
Another advantage is providing the ability to quit out of the authentication mechanism and be connected to a live agent, who can then perform the authentication for the caller using information stored in his database and then transfer the caller to the part of the system which would have required her authentication credentials.
A basic system which uses a static pin number for authentication:
[Read_Authentication] exten => start,1,NoOp() same => n,Playback(silence/1) same => n,Set(VerificationPin=1234) ; set pin to verify against same => n,Set(TriesCounter=1) ; counter for login attempts same => n(get_pin),Read(InputPin,enter-password) ; get a pin from ; from the caller ; Check if the pin input by the caller is the same as our verification pin. same => n,GotoIf($["${InputPin}" = "${VerificationPin}"]?pin_accepted,1) ; Increment the TriesCounter by 1 same => n,Set(TriesCounter=${INC(TriesCounter)}) same => n,GotoIf($[${TriesCounter} > 3]?too_many_tries,1) same => n,Playback(vm-incorrect) same => n,Goto(get_pin) exten => pin_accepted,1,NoOp() same => n,Playback(auth-thankyou) same => n,Hangup() exten => too_many_tries,1,NoOp() same => n,Playback(vm-incorrect) same => n,Playback(vm-goodbye) same => n,Hangup()
We can modify the dialplan slightly to provide greater security by requiring an account code and a pin, which can be loaded from the AstDB:
[Read_Authentication] exten => start,1,NoOp() same => n,Playback(silence/1) ; Set a couple of counters for login attempts same => n,Set(TriesCounter=1) same => n,Set(InvalidAccountCounter=1) ; Request the access code (account code) same => n(get_acct),Read(InputAccountNumber,access-code) ; make sure we have an account number same => n,GotoIf($[${ISNULL(${InputAccountNumber})}]?get_acct) ; Request the password (pin) same => n(get_pin),Read(InputPin,vm-password) ; Check the database to see if a password exists for the account number entered same => n,GotoIf($[${DB_EXISTS(access_codes/${InputAccountNumber})}]? have_account,1:no_account,1) ; If a pin number exists check it against what was entered exten => have_account,1,NoOp() same => n,Set(VerificationPin=${DB_RESULT}) same => n,GotoIf($["${InputPin}" = "${VerificationPin}"]?pin_accepted,1) same => n,Set(TriesCounter=${INC(TriesCounter)}) same => n,GotoIf($[${TriesCounter} > 3]?too_many_tries,1) same => n,Playback(vm-incorrect) same => n,Goto(start,get_pin) ; If no account exists, request a new access code be entered exten => no_account,1,NoOp() same => n,Playback(invalid) same => n,Set(InvalidAccountCounter=${INC(InvalidAccountCounter)}) same => n,GotoIf($[${InvalidAccountCounter} > 3]?too_many_tries,1) same => n,Goto(start,get_acct) ; Account and pin were verified exten => pin_accepted,1,NoOp() same => n,Playback(auth-thankyou) same => n,Hangup() ; Sorry, too many attempts to login. Hangup. exten => too_many_tries,1,NoOp() same => n,Playback(vm-incorrect) same => n,Playback(vm-goodbye) same => n,Hangup()
We added the access code
(555
) and pin (1234
) to the AstDB using the following command:
*CLI> database put access_codes 555 1234
If you’ve already looked at the solutions in Recipes and , what you’ll immediately notice is that the other solutions are quite a bit more compact. The main reason is that all of the loop control is handled within the dialplan application, whereas in this case we’re defining the loop control with dialplan logic and handling the number of loops and what to do on failure ourselves. With greater control comes greater complexity, and that is illustrated in the examples provided. The advantage to this greater level of verbosity is the control at each step: which prompts are played, when they’re played, and how often they are played. We also get to control the method of authentication.
In our first example, we
showed a fairly basic authentication method which simply asked the
caller for a password that we statically defined within the
dialplan.[1] After entering the pin, we check what the user entered
against what we set in the VerificationPin
channel variable. If the
numbers do not match, we increment a counter, test to see the number has
increased to greater than 3, and, if not, request that the caller
re-enter her pin. If the number of tries exceeds 3, then we play a
prompt saying goodbye and hang up.
Our second example was
expanded to include both an access code (account code) and a password
(pin) which we’ve written to the AstDB. When the caller enters the
dialplan, we request an access code and a password. We then check the
AstDB using DB_EXISTS()
to determine
if the account number exists in the database. If it does not, then we
inform the user that the account does not exist using a dialplan defined
by the no_account
extension. This is
followed by a check to determine if the caller has entered an invalid
account number and, if so, determine if this has happened more than 3
times, in which case we then disconnect the caller.
If the caller has entered a
valid account number, we then handle additional logic in the have_account
extension, where we verify the
pin number entered against what was returned from the database. If
everything is valid, then we play back a prompt in the pin_accepted
extension, and hang up the call
(although it’s implied additional logic could then be handled now that
the caller has been validated).
Because the solutions
described contain a lot of dialplan logic and aren’t tied to any
particular dialplan application (other than Read()
, which we’re using for data input), the
solutions could easily be modified to authenticate against other
external sources of data. For example, the REALTIME()
functions could be used to gather
credentials from an LDAP database, or func_odbc could be employed to gather
information from a relational database. You could even use CURL()
to pass the data collected from the
caller to authenticate against a web page which could then return
account information to the caller. The possibilities with custom
dialplan really are only limited by the problems encountered and solved
by your imagination.
You would like to insert debugging into your dialplan that can be enabled on a global, per-device, or per-channel basis.
Use something like this
chanlog
GoSub()
routine:
[chanlog] exten => s,1,GotoIf($[${DB_EXISTS(ChanLog/all)} = 0]?checkchan1) same => n,GotoIf($[${ARG1} <= ${DB(ChanLog/all)}]?log) same => n(checkchan1),Set(KEY=ChanLog/channel/${CHANNEL}) same => n,GotoIf($[${DB_EXISTS(${KEY})} = 0]?checkchan2) same => n,GotoIf($[${ARG1} <= ${DB(${KEY})}]?log) same => n(checkchan2),Set(KEY=ChanLog/channel/${CUT(CHANNEL,-,1)}) same => n,GotoIf($[${DB_EXISTS(${KEY})} = 0]?return) same => n,GotoIf($[${ARG1} <= ${DB(${KEY})}]?log) same => n(return),Return() ; Return without logging same => n(log),Verbose(0,${ARG2}) same => n,Return()
The chanlog
GoSub()
routine takes two arguments:
Channel logging using
this routine will be sent to the Asterisk console at verbose level 0,
meaning that they will show up when you want them to, regardless of the
current core set verbose setting.
This routine uses a different method, values in AstDB, to control what
messages show up. See Table 1-1 for the
AstDB entries read in by the chanlog
routine. If the log level argument is less than or equal to one of these
matched entries in the AstDB, then the message will be logged.
Family | Key | Description |
ChanLog/ | all | This setting is applied to all channels. |
ChanLog/ | channels/ | The chanlog routine will also look for
an entry that matches the part of the channel name that comes
before the - . For example, if
the channel name is SIP/myphone-00112233 , the routine will
look for a key of channels/SIP/myphone . |
ChanLog/ | channels/ | The chanlog routine
also checks for a match against the full channel name. |
Once this routine has
been added to your dialplan, you can start adding log statements to any
extensions on your system. As an example, here is a typical company main
menu that now has chanlog
messages
for each step:
exten => 6000,1,GoSub(chanlog,s,1(1,[${CHANNEL}] Entered main menu)) same => n,Background(main-menu) same => n,WaitExten(5) exten => 1,1,GoSub(chanlog,s,1(1,[${CHANNEL}] Pressed 1 for Sales)) same => n,Goto(sales,s,1) exten => 2,1,GoSub(chanlog,s,1(1,[${CHANNEL}] Pressed 2 for Technical Support)) same => n,Goto(tech_support,s,1) exten => 3,1,GoSub(chanlog,s,1(1,[${CHANNEL}] Pressed 3 for Customer Service)) same => n,Goto(customer_service,s,1) exten => 4,1,GoSub(chanlog,s,1(1,[${CHANNEL}] Pressed 4 for Training)) same => n,Goto(training,s,1) exten => 5,1,GoSub(chanlog,s,1(1,[${CHANNEL}] Pressed 5 for Directory)) same => n,Directory() exten => 0,1,GoSub(chanlog,s,1(1,[${CHANNEL}] Pressed 0 for Operator)) same => n,Goto(operator,s,1) exten => i,1,GoSub(chanlog,s,1(1,[$CHANNEL}] invalid "${INVALID_EXTEN}")) same => n,Goto(6000,1) exten => t,1,GoSub(chanlog,s,1(1,[${CHANNEL}] Timed out waiting for digit)) same => n,Goto(6000,1)
To see the output, we
will first disable all other verbose messages from Asterisk and then
turn on chanlog
messages for all
channels:
*CLI>
core set verbose 0
*CLI>
database put ChanLog all 1
Now when someone calls into the main menu, he will see messages like this at the Asterisk console:
[SIP/000411223344-000000b8] Entered main menu [SIP/000411223344-000000b8] Pressed 4 for Training
For more information about other types of logging and monitoring of Asterisk systems, see Chapter 24, “System Monitoring and Logging,” of Asterisk: The Definitive Guide (O’Reilly).