{"id":14291,"url":"\/distributions\/14291\/click?bit=1&hash=257d5375fbb462be671b713a7a4184bd5d4f9c6ce46e0d204104db0e88eadadd","hash":"257d5375fbb462be671b713a7a4184bd5d4f9c6ce46e0d204104db0e88eadadd","title":"\u0420\u0435\u043a\u043b\u0430\u043c\u0430 \u043d\u0430 Ozon \u0434\u043b\u044f \u0442\u0435\u0445, \u043a\u0442\u043e \u043d\u0438\u0447\u0435\u0433\u043e \u0442\u0430\u043c \u043d\u0435 \u043f\u0440\u043e\u0434\u0430\u0451\u0442","buttonText":"","imageUuid":""}

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

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

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

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

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

  • Users – таблица с информацией о пользователях.
  • UrlList – хранит URL-адрес, состояние сервера на момент проверки и ID пользователя, добавившего адрес.
  • 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-бот. У него есть несколько основных команд:

  • Add url – добавляет новый URL-адрес;
  • Remove url – удаляет данный URL-адрес;
  • Show – показывает список URL-адресов текущего пользователя;
  • Analyze url – производит анализ логов данного URL-адреса;
  • 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: https://github.com/ValentinaFedorova/Ping_Server_Bot.

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

0
Комментарии
-3 комментариев
Раскрывать всегда