Telegram-бот мониторинга состояния серверов на Haskell

от автора

Привет, Хабр! Состояние сервера необходимо постоянно мониторить, чтобы в случае ошибки быстро ее исправить. Удобнее всего отслеживать работоспособность с помощью смартфона, а именно Telegram-бота, он позволяет осуществлять проверку на ходу. Реализуем Telegram-бот на функциональном языке Haskell, заодно разберемся в его преимуществах.  

Одним из преимуществ функциональных языков является удобство обработки списков из-за встроенного механизма рекурсий и сравнения образцов, но многие разработчики не используют их для разработки реальных приложений, в которых такие задачи встречаются. Рассмотрим возможность применения функционального языка Haskell на задаче мониторинга состояния серверов и анализа их логов. Задача является важной, так как чем дольше сервер находится в неработоспособном состоянии, тем больше проблем это приносит его пользователям.

Основная задача программы – регулярно осуществлять проверку серверов пользователя, записывать результаты в базу данных и при необходимости, выдавать собранную статистику.

Для функционирования программы необходимо создать базу данных и три таблицы:

  1. Users – таблица с информацией о пользователях;

  2. UrlList – хранит URL-адрес, состояние сервера на момент проверки и ID пользователя, добавившего адрес;

  3. UrlState – в данной таблице сохраняются логи обращений к серверу, а именно: имя сервера, состояние и дата состояния.

Схема таблиц

В данной программе взаимодействие с базой данных производится с помощью модуля Database.SQLite.Simple. Рассмотрим пример функции, которая добавляет новый URL-адрес. Необходимо открыть соединение с базой данных и передать функции SQ.execute открытое соединение и текст запроса на языке SQL, затем закрыть соединение.

Листинг 1. Пример вставки новой строки в таблицу.

insertNewUrl :: String -> Int -> IO () insertNewUrl urlstr usrId = do   db_conn <- SQ.open "test2.db"   SQ.execute db_conn "INSERT INTO UrlList (site_url, state, userId) VALUES (?, 1, ?)"   ( urlstr :: String, usrId :: Int)   SQ.close db_conn

Все взаимодействие с пользователем осуществляется через Telegram-бот. У него есть несколько основных команд:

  1. Add url – добавляет новый URL-адрес;

  2. Remove url – удаляет данный URL-адрес;

  3. Show – показывает список URL-адресов текущего пользователя;

  4. Analyze url – производит анализ логов данного URL-адреса;

  5. Ping url – отправляет запрос на данный URL-адрес, ждет ответ и выводит актуальную статистику.

Для взаимодействия с Telegram-ботом используется функция handleAction, которая обрабатывает все активности в чате. Функция определяет пользователя: если текущего пользователя нет в базе данных, то в чат отправляется сообщение со списком возможных команд и новый пользователь записывается в таблицу Users. Если пользователь известен, то команды из чата анализируются и обрабатываются с помощью конструкции case. Для каждой команды предусмотрены свои функции и ответы в чате.

Листинг 2. Пример взаимодействия с Telegram-ботом.

handleAction :: Action -> ChatModel -> Eff Action ChatModel handleAction action model =   case action of     NoAction -> pure model     RecordMsg usrId usrname chtId msgId txt ->       model <# do         let cId = fromIntegral chtId ::Int         maybeUser <- liftIO $ getUserById usrId         case maybeUser of           Just user -> do             now <- liftIO getCurrentTime             comand <- liftIO $ getCommand txt              liftIO $ print comand             url <- liftIO $ getUrl txt             liftIO $ print cId             let chtId2 = ChatId chtId             let chatId=SomeChatId chtId2             case comand of               "add" -> do                 liftIO $ insertNewUrl url usrId                 replyString "url added"               "remove" -> do                 liftIO $ delSrv url                 replyString "url removed"               "show" -> do                 sitelist<-liftIO $ getUrlList usrId                 replyString (getUrlListTxt sitelist)               "help" -> do                 replyString (unpack  startMessage)               "analyze" -> do                 stateList <- liftIO $ getUrlState url                 replyString (analyseLog stateList 1 0 url)               "ping" -> do                 liftIO $ print ("pinging"++url)                 liftIO $ pingServer url                 sitelist<-liftIO $ getUrlList usrId                 replyString (getUrlListTxt sitelist)               "pingAll" -> do                   liftIO $ doTesting                   sitelist<-liftIO $ getUrlList usrId                   replyString (getUrlListTxt sitelist)               _ -> do                 replyString (unpack  startMessage)                          Nothing -> do             userKy <- liftIO $ (createUser usrId usrname cId)             replyString "Hi. There's a command list: \n 1) add url \n 2) remove url \n 3) show - print all urls \n 4) ping - to ping all added url"

Рассмотрим подробнее основные функции. Функция ping принимает на вход URL-адрес и пытается получить ответ от сервера. Для работы с http-запросами в Haskell используется модуль Network.HTTP.Simple. С помощью функции httpLBS получаем ответ от сервера: если произошла ошибка и к серверу невозможно подключиться, то фиксируем состояние ошибки в базе данных в таблице с логами. Если ошибки не произошло, то фиксируем нормальное состояние.

Листинг 3. Пример работы с HTTP-запросами.

 "ping" -> do                 liftIO $ print ("pinging"++url)                 liftIO $ pingServer url                 sitelist<-liftIO $ getUrlList usrId                 replyString (getUrlListTxt sitelist) pingServer :: String -> IO() pingServer url = do      req <- parseRequest url     response2 <- try $ httpLBS req      case response2 of         Left e -> saveErrorState url e         Right result -> saveOkState url

Функция analyze изначально вызывает функцию getUrlState для получения логов состояний по данному URL-адресу, при этом логи возвращаются отсортированными от более новых записей к более старым. Затем полученная информация передается в функцию analyseLog, которая принимает следующие параметры: массив логов, состояние, количество попыток соединения и имя сервера. Рекурсия, заложенная в функциональных языках, позволяет удобно обрабатывать логи, хранящиеся в массиве.

Листинг 4. Пример рекурсивной обработки списка.

"analyze" -> do                 stateList <- liftIO $ getUrlState url                 replyString (analyseLog stateList 1 0 url)  getUrlState :: String -> IO ([UrlState]) getUrlState url= do   db_conn <- SQ.open "test2.db"   res <- SQ.queryNamed db_conn "SELECT site_url, url_state,statetime FROM UrlState WHERE site_url = :site_url ORDER BY rowid DESC" [":site_url" := url] :: IO [UrlState]   SQ.close db_conn   return res  analyseLog :: [UrlState] -> Int -> Int -> String -> String analyseLog ((UrlState url 1 wtime):t) 1 cnt site = site++", all is ok. Pinging started at "++show wtime analyseLog ((UrlState url 0 _):t) 1 cnt site = analyseLog t 0 (cnt+1) site analyseLog ((UrlState url 0 _):t) 0 cnt site = analyseLog t 0 (cnt+1) site analyseLog [] 1 cnt site = site ++ " - missing. You can add him. Comand: add "++site analyseLog [] 0 cnt site = site ++ " - never working. The call count is "++show cnt analyseLog ((UrlState url 1 time1):t) 0 cnt site=  "server " ++ url ++ " is not working. The last working time is "++ formatTime defaultTimeLocale "%Y/%m/%d %H:%M" time1

Основная задача функции analyseLog – сформировать статистику работоспособности сервера. Если состояние сервера на момент последней проверки в логе равно «1», то функция возвращает сообщение о том, что сервер функционирует нормально, а также время последней проверки.

Если состояние сервера на момент последней проверки – «0», но до этого в логе видно, что сервер работал, то выдается сообщение о том, что сервер находится в неработоспособном состоянии, а также его последнее время работы.

Если в логах всегда встречается состояние «0», то выводится сообщение о том, что сервер никогда не работал, а также количество попыток связаться с ним.

В приведенном примере мы можем увидеть реализацию полноценного приложения на функциональном языке. В приложении сочетаются методы взаимодействия с HTTP серверами, Telegram-ботом, базой данных и применение рекурсий для анализа логов. Для чистых функций Haskell хранит результаты вычисления в кэше, поэтому, если функция вызывается с теми же параметрами, она не будет выполняться, а возьмет результат из кэша. Эта особенность позволяет сэкономить время на выполнение некоторых функций. Полный код программы вы можете найти на моем GitHub: ссылка на GitHub.

Поделитесь своим опытом разработки на функциональных языках в комментариях!


ссылка на оригинал статьи https://habr.com/ru/post/675638/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *