Есть колл-центр. Есть Asterisk/FreePBX с настроенными очередями. Есть агенты, которые должны обслуживать вызовы. Но потенциальных клиентов так много, а агентов так мало, что первые никак не могут дозвониться до вторых — повисят-повисят в очереди минуту, да и отключаются.

Но зачем-то они же звонили! Может, они хотят денег занести в компанию? Попробуем вернуть и клиентов и их деньги на примере FreePBX.

В настройках очереди можно указать Fail Over Destination — то, куда направлять вызов, когда очередь переполнена, истекло время ожидания и т.п. Но зачастую бывает так, что звонивший отключается раньше, чем его успевает перенаправить в Fail Over Destination — мало ли, связь оборвалась. Готового решения для таких случаев нет. Поэтому идём под кат и пишем своё — с отправкой оповещения в Telegram/Slack/E-mail/куда-то там еще.

И первое, что нам надо понять — как FreePBX пишет логи очередей. В самом простом случае это текстовый файл /var/log/asterisk/queue_log.

Так в лог пишется обычный звонок от абонента 8964467ХХХХ, позвонившего по номеру 302221ХХХХ, попавшего в очередь №603 на первую позицию, которому через 8 секунд ответил Agent Girl. Они проговорили 133 секунды и первым трубку положил абонент — всем бы таких вежливых менеджеров!

1584318765|1584318747.89449|603|NONE|DID|302221ХХХХ
1584318765|1584318747.89449|603|NONE|ENTERQUEUE||8964467ХХХХ|1
1584318774|1584318747.89449|603|Agent Girl|CONNECT|9|1584318765.89450|8
1584318907|1584318747.89449|603|Agent Girl|COMPLETECALLER|9|133|1

А вот немного другая история, когда абонент 8996453ХХХХ дозвонился по номеру 302257ХХХХ, повисел 10 секунд в очереди №210 в первой позиции и отключился. Даже голосовое меню не дослушал!

1581728710|1581728690.59367|210|NONE|DID|302257ХХХХ
1581728710|1581728690.59367|210|NONE|ENTERQUEUE||8996453ХХХХ|1
1581728720|1581728690.59367|210|NONE|ABANDON|1|1|10

Вот про него-то мы и напишем в мессенджер.

Для этого нам надо отловить появление события ABANDON в файле лога. Я долго ломал себе голову как это можно сделать, но потом совершенно случайно наткнулся на прекрасный инструмент — MONIT. В нашем случае он будет следить за логом очередей, и как только в нём появится строка «ABANDON» будет вызывать скрипт обработки данного события.

Для этого после установки пакета:

yum install monit

Настроим его — создадим файл /etc/monit.d/queue_log и запишем в него буквально три строчки:

check file queue_log with path /var/log/asterisk/queue_log
    if content = "ABANDON" then
        exec "/var/lib/asterisk/agi-bin/qlogevent.sh $EVENT"

Таким образом, при появлении слова «ABANDON» в логе очередей, что говорит нам о преждевременном отключении абонента, будет вызван скрипт /var/lib/asterisk/agi-bin/qlogevent.sh с параметром $EVENT, что есть ничто иное, как полностью строка, в которой это слово появилось.
Это просто.

А теперь начинаются не шахматы — тут думать надо.

В строке, переданной в скрипт, отсутствует информация о номере звонившего:

1581728720|1581728690.59367|210|NONE|ABANDON|1|1|10

В ней есть только Timestamp (1581728720), уникальный ID звонка (1581728690.59367), номер очереди (210), само событие и еще несколько несущественных параметров. Т.е. искать номер абонента придется нам самим. А каким же образом? А очень просто, ведь UID звонка у нас есть!

Обратимся опять к логу данного звонка, конкретно — к строке, в которой абонент попадает в очередь:

1581728710|1581728690.59367|210|NONE|ENTERQUEUE||8996453ХХХХ|1

При возникновении события «ENTERQUEUE» один из параметров — необходимый нам номер абонента. Нам остается только отыскать данную строку в файле queue_log. Этим как раз занимается скрипт на bash, который MONIT нам заботливо вызвал:

#!/bin/bash
#MONIT_DESCRITION - строка, которая передана как параметр в скрипт.
#Преобразуем timestamp в человеческое представление даты-времени (параметр №1).
timedate=$(echo $MONIT_DESCRIPTION | cut -d\| -f1 | cut -d\: -f2 | gawk '{print strftime("%d.%m.%Y %H:%M:%S",$0);}')
#Получаем UID (параметр №2)
call_id=$(echo $MONIT_DESCRIPTION | cut -d\| -f2)
#и номер очереди (параметр №3)
qnum=$(echo $MONIT_DESCRIPTION | cut -d\| -f3)

#По номеру очереди присваиваем ей человеческое имя
case $qnum in
    210)
        qname="Очередь 1"
        ;;
    220)
        qname="Очередь 2"
        ;;
esac

#И ищем в логе строку с UID, номером очереди и событием ENTERQUEUE
str=$(grep "|$call_id|$qnum|.*|ENTERQUEUE" /var/log/asterisk/queue_log)
#Получаем из неё номер позвонившего абонента (параметр №7)
caller_id=$(echo $str | cut -d\| -f7)
#Время его попадания в очередь (паоаметр №1)
time_enter=$(echo $str | cut -d\| -f1)
#И время пропадания из очедери (параметр №1 из события ABANDON)
time_abandon=$(echo $MONIT_DESCRIPTION | cut -d\| -f1 | cut -d\: -f2)
#Формируем сообщение
data="Абонент $caller_id $timedate позвонил в $qname, не дождался ответа и отбился через $(($time_abandon-$time_enter)) сек."
#И отправляем его в мессенджер/почту. В данном случае это Slack.
/usr/bin/curl -X POST -H 'Content-type: application/json' --data '{"text":"'"$data"'"}' "https://hooks.slack.com/services/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

Собственно, и всё!

Правда, есть один казус — MONIT, предназначенный для мониторинга всего и вся, сам нуждается в мониторинге, т.к. периодически падает.