ChatOps на практике: создание бота для мониторинга логов
- среда, 14 августа 2024 г. в 00:00:07
Недавно на работе меня попросили придумать рабочую задачку для студентов. Поскольку я работаю в инфраструктурной команде, мои повседневные задачи вряд ли подходят для их домашек или курсовых работ. Чтобы найти подходящую идею, я начал перебирать инструменты, которыми мы с командой часто пользуемся. Большинство из них интегрированы с чатами и ботами, и один из ключевых инструментов — это Алерт Бот. Он отслеживает логи и отправляет оповещения, если происходит что-то необычное. Это позволяет нам быстрее обнаруживать и устранять инциденты.
Когда я придумал эту задачу для студентов, мне пришло в голову, что подобный функционал может быть полезен многим. Возможно, кто-то сможет адаптировать это решение под свои нужды и облегчить себе жизнь, автоматизируя процесс мониторинга и реагирования на инциденты. Собственно этим я бы и хотел поделиться в своей статье.
Итак, студентам предлагается разработать бота, который будет анализировать логи сервера и отправлять уведомления в Telegram, если обнаруживает ошибки или другие аномалии. Основные требования к проекту:
Мониторинг логов: Бот должен периодически проверять указанный файл логов и искать в нем ошибки.
Фильтрация данных: Необходимо отфильтровать важные сообщения (например, с уровнем ERROR или CRITICAL) и игнорировать менее значимые (DEBUG, INFO).
Отправка уведомлений: При обнаружении ошибок бот должен отправлять сообщение в Telegram-чат.
Конфигурируемость: Настройки, такие как путь к логам, частота проверки и идентификатор чата, должны быть легко изменяемы.
Наш бот будет состоять из следующих основных компонентов:
Лог-файл: Файл, содержащий журналы событий сервера, который бот будет анализировать.
Анализатор логов: Модуль, который читает файл логов и извлекает из него важные сообщения.
Telegram-бот: Модуль, который отправляет найденные ошибки в указанный чат.
Эти компоненты должны взаимодействовать между собой, обеспечивая непрерывный мониторинг логов и оперативное уведомление о проблемах.
Давайте начнем с моделирования деятельности сервера, чтобы у нас были логи для анализа. Мы создадим скрипт на Go, который будет генерировать логи с разными уровнями сообщений (например, INFO, WARNING, ERROR) в случайном порядке.
Предположим, что наш "сервер" будет периодически генерировать события с различными уровнями важности. Для этого напишем скрипт, который будет записывать логи в файл.
Сначала создадим функции, которые будут считывать логи и писать их:
func readLines(fileName string) ([]string, error) {
file, err := os.Open(fileName)
if os.IsNotExist(err) {
return []string{}, nil
} else if err != nil {
return nil, err
}
defer file.Close()
var lines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
return lines, scanner.Err()
}
func writeLines(lines []string, fileName string) error {
file, err := os.Create(fileName)
if err != nil {
return err
}
defer file.Close()
writer := bufio.NewWriter(file)
for _, line := range lines {
fmt.Fprintln(writer, line)
}
return writer.Flush()
}
Теперь создадим функции, которые будут генерировать нам непосредственно сами логи
var (
logLevels = []string{"DEBUG", "INFO", "ERROR"}
mu sync.Mutex
)
func generateLog() {
mu.Lock()
defer mu.Unlock()
lines, err := readLines(logFileName)
if err != nil {
fmt.Println("Error reading log file:", err)
return
}
if len(lines) >= maxLines {
lines = lines[len(lines)-maxLines+1:]
}
logLine := fmt.Sprintf("%s [%s] %s\n", time.Now().Format(time.RFC3339), logLevels[rand.Intn(len(logLevels))], generateRandomMessage())
lines = append(lines, logLine)
err = writeLines(lines, logFileName)
if err != nil {
fmt.Println("Error writing to log file:", err)
}
}
func generateRandomMessage() string {
messages := []string{
"User logged in",
"File uploaded",
"Error processing request",
"User logged out",
"Database connection established",
"Invalid input received",
}
return messages[rand.Intn(len(messages))]
}
Теперь объединим всё вместе в основной функции, которая будет периодически генерировать и записывать лог-сообщения:
const (
logFileName = "logs.log"
maxLines = 200
)
func main() {
rand.Seed(time.Now().UnixNano())
go func() {
for {
generateLog()
time.Sleep(time.Millisecond * 100)
}
}()
select {}
}
Важно помнить, что логи не должны храниться бесконечно. Со временем их количество может значительно возрасти, что приведет к увеличению объема файла и возможным проблемам с производительностью. Чтобы этого избежать, следует установить разумные ограничения на количество сохраняемых записей. В данном примере мы ограничиваемся хранением последних 200 логов
Теперь, когда у нас есть сгенерированные логи, приступим к разработке бота, который будет их анализировать и отправлять уведомления в Telegram. Ниже описаны основные шаги для создания такого бота.
Для работы с Telegram и анализа логов нам потребуются несколько внешних библиотек. В Go это можно сделать с помощью go get
:
go get -u github.com/go-telegram-bot-api/telegram-bot-api/v5
Эта библиотека предоставит нам интерфейс для взаимодействия с Telegram.
Первый шаг в нашем боте — это чтение логов и поиск в них ошибок. Мы будем использовать функцию для чтения файла и фильтрации важных сообщений.
package main
import (
"bufio"
"fmt"
"os"
"strings"
"time"
)
func readLogs(logFilePath string) ([]string, error) {
file, err := os.Open(logFilePath)
if err != nil {
return nil, err
}
defer file.Close()
var importantLogs []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
logLine := scanner.Text()
if strings.Contains(logLine, "ERROR") || strings.Contains(logLine, "CRITICAL") {
importantLogs = append(importantLogs, logLine)
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
return importantLogs, nil
}
Следующим шагом будет отправка уведомлений в Telegram. Для этого воспользуемся библиотекой telegram-bot-api
.
package main
import (
"log"
"github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
func sendTelegramNotification(bot *tgbotapi.BotAPI, chatID int64, message string) {
msg := tgbotapi.NewMessage(chatID, message)
_, err := bot.Send(msg)
if err != nil {
log.Printf("Error sending message: %v", err)
}
}
Теперь объединим всё вместе в основной функции. Она будет периодически проверять логи и отправлять уведомления, если обнаружены ошибки.
package main
import (
"fmt"
"log"
"time"
"os"
"github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
func main() {
logFilePath := "server.log"
botToken := "YOUR_TELEGRAM_BOT_TOKEN"
chatID := int64(YOUR_CHAT_ID)
bot, err := tgbotapi.NewBotAPI(botToken)
if err != nil {
log.Panic(err)
}
log.Printf("Authorized on account %s", bot.Self.UserName)
for {
importantLogs, err := readLogs(logFilePath)
if err != nil {
log.Printf("Error reading log file: %v", err)
continue
}
for _, logMessage := range importantLogs {
sendTelegramNotification(bot, chatID, logMessage)
}
time.Sleep(10 * time.Second) // Проверяем логи каждые 10 секунд
}
}
Для удобства можно вынести параметры, такие как путь к логам, токен бота и идентификатор чата, в конфигурационный файл или использовать переменные окружения. Это упростит адаптацию бота к разным условиям эксплуатации.
Пример использования переменных окружения:
package main
import (
"os"
"log"
)
func main() {
logFilePath := os.Getenv("LOG_FILE_PATH")
botToken := os.Getenv("TELEGRAM_BOT_TOKEN")
chatID := os.Getenv("TELEGRAM_CHAT_ID")
if logFilePath == "" || botToken == "" || chatID == "" {
log.Fatal("Missing necessary environment variables")
}
// Остальной код...
}
Теперь, когда бот готов, его можно развернуть на сервере или в облаке и использовать для мониторинга логов в реальных условиях. Опытные разработчики, вероятно, уже знакомы с подобными задачами, но надеюсь, что эта статья оказалась полезной для тех, кто только начинает свой путь в программировании, работает над своими пет-проектами или делает первые шаги в индустрии. Создание таких инструментов — отличный способ приобрести навыки, которые пригодятся в реальной работе.