Недавно на работе меня попросили придумать рабочую задачку для студентов. Поскольку я работаю в инфраструктурной команде, мои повседневные задачи вряд ли подходят для их домашек или курсовых работ. Чтобы найти подходящую идею, я начал перебирать инструменты, которыми мы с командой часто пользуемся. Большинство из них интегрированы с чатами и ботами, и один из ключевых инструментов — это Алерт Бот. Он отслеживает логи и отправляет оповещения, если происходит что-то необычное. Это позволяет нам быстрее обнаруживать и устранять инциденты.
Когда я придумал эту задачу для студентов, мне пришло в голову, что подобный функционал может быть полезен многим. Возможно, кто-то сможет адаптировать это решение под свои нужды и облегчить себе жизнь, автоматизируя процесс мониторинга и реагирования на инциденты. Собственно этим я бы и хотел поделиться в своей статье.
Описание задачи
Итак, студентам предлагается разработать бота, который будет анализировать логи сервера и отправлять уведомления в Telegram, если обнаруживает ошибки или другие аномалии. Основные требования к проекту:
Мониторинг логов: Бот должен периодически проверять указанный файл логов и искать в нем ошибки.
Фильтрация данных: Необходимо отфильтровать важные сообщения (например, с уровнем ERROR или CRITICAL) и игнорировать менее значимые (DEBUG, INFO).
Отправка уведомлений: При обнаружении ошибок бот должен отправлять сообщение в Telegram-чат.
Конфигурируемость: Настройки, такие как путь к логам, частота проверки и идентификатор чата, должны быть легко изменяемы.
Архитектура решения
Наш бот будет состоять из следующих основных компонентов:
Лог-файл: Файл, содержащий журналы событий сервера, который бот будет анализировать.
Анализатор логов: Модуль, который читает файл логов и извлекает из него важные сообщения.
Telegram-бот: Модуль, который отправляет найденные ошибки в указанный чат.
Эти компоненты должны взаимодействовать между собой, обеспечивая непрерывный мониторинг логов и оперативное уведомление о проблемах.
Генерация логов
Давайте начнем с моделирования деятельности сервера, чтобы у нас были логи для анализа. Мы создадим скрипт на Go, который будет генерировать логи с разными уровнями сообщений (например, INFO, WARNING, ERROR) в случайном порядке.
Предположим, что наш "сервер" будет периодически генерировать события с различными уровнями важности. Для этого напишем скрипт, который будет записывать логи в файл.
1. Создание функции для чтения и записи строчек в файл
Сначала создадим функции, которые будут считывать логи и писать их:
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()
}
2. Функции для генерации логов
Теперь создадим функции, которые будут генерировать нам непосредственно сами логи
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))]
}
3. Основная функция для генерации логов
Теперь объединим всё вместе в основной функции, которая будет периодически генерировать и записывать лог-сообщения:
const (
logFileName = "logs.log"
maxLines = 200
)
func main() {
rand.Seed(time.Now().UnixNano())
go func() {
for {
generateLog()
time.Sleep(time.Millisecond * 100)
}
}()
select {}
}
Важно помнить, что логи не должны храниться бесконечно. Со временем их количество может значительно возрасти, что приведет к увеличению объема файла и возможным проблемам с производительностью. Чтобы этого избежать, следует установить разумные ограничения на количество сохраняемых записей. В данном примере мы ограничиваемся хранением последних 200 логов
Продолжение разработки бота на Go
Теперь, когда у нас есть сгенерированные логи, приступим к разработке бота, который будет их анализировать и отправлять уведомления в Telegram. Ниже описаны основные шаги для создания такого бота.
1. Установка необходимых библиотек
Для работы с Telegram и анализа логов нам потребуются несколько внешних библиотек. В Go это можно сделать с помощью go get
:
go get -u github.com/go-telegram-bot-api/telegram-bot-api/v5
Эта библиотека предоставит нам интерфейс для взаимодействия с Telegram.
2. Чтение и анализ логов
Первый шаг в нашем боте — это чтение логов и поиск в них ошибок. Мы будем использовать функцию для чтения файла и фильтрации важных сообщений.
package main
import (
"bufio"
"fmt"
"os"
"strings"
"time"
)
func readLogs(logFilePath string, logChannel chan<- string) error {
file, err := os.Open(logFilePath)
if err != nil {
return err
}
defer file.Close()
reader := bufio.NewReader(file)
for {
line, err := reader.ReadString('\n')
if err != nil {
if err.Error() == "EOF" {
continue
} else {
log.Printf("Error reading log file: %v", err)
continue
}
}
line = strings.TrimSpace(line)
if strings.Contains(line, "ERROR") || strings.Contains(line, "CRITICAL") {
logChannel <- line
}
}
}
3. Отправка уведомлений в Telegram
Следующим шагом будет отправка уведомлений в 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)
}
}
4. Основная функция для мониторинга логов и отправки уведомлений
Теперь объединим всё вместе в основной функции. Она будет периодически проверять логи и отправлять уведомления, если обнаружены ошибки.
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)
logChannel := make(chan string)
// Запуск горутины для непрерывного чтения логов
go func() {
err := readLogs(logFilePath, logChannel)
if err != nil {
log.Fatalf("Error reading log file: %v", err)
}
}()
// Основной цикл для обработки сообщений из канала и отправки их в Telegram
for logMessage := range logChannel {
sendTelegramNotification(bot, chatID, logMessage)
}
}
5. Конфигурируемость
Для удобства можно вынести параметры, такие как путь к логам, токен бота и идентификатор чата, в конфигурационный файл или использовать переменные окружения. Это упростит адаптацию бота к разным условиям эксплуатации.
Пример использования переменных окружения:
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")
}
// Остальной код...
}
Теперь, когда бот готов, его можно развернуть на сервере или в облаке и использовать для мониторинга логов в реальных условиях. Опытные разработчики, вероятно, уже знакомы с подобными задачами, но надеюсь, что эта статья оказалась полезной для тех, кто только начинает свой путь в программировании, работает над своими пет-проектами или делает первые шаги в индустрии. Создание таких инструментов — отличный способ приобрести навыки, которые пригодятся в реальной работе.
Комментарии (6)
AndreiNekrasOn
13.08.2024 18:41В такой реализации получается, что читаем полностью весь файл логов и на каждую ошибку отсылаем по уведомлению, и повторно отсылаем те же уведомления (+ возможно новые) каждые десять секунд?
BincomAD Автор
13.08.2024 18:41+2Да, изначально подумал, что как тестовый пример пойдет, но сейчас вижу, что многие сохраняют и возможно будут использовать этот код, поэтому подправил код и предлагаю запустить постоянно читающую логи горутину, писать их в канал, а из канала потом вычитывать)
manyakRus
13.08.2024 18:41+1https://github.com/ManyakRus/telegram_loki
я уже сделал такой :-) очень полезныйOnetouchman
13.08.2024 18:41Я немного не понимаю чем плох встроенный алерт менеджер с рендером и отправкой сообщений из графаны в телеграмм с картинками по настроеным алертам на панель например с теми же логами из локи
johhy13
Но кажется что смысла возвращать пустой слайс нет (далее может быть проблема)
BincomAD Автор
Привет, спасибо за замечание) А какие могут быть потенциально проблемы с пустой строкой?
В моем понимании это как бы означает не ошибку, а то что логи еще не писались в файл, нет логов - нет файла, собственно поэтому логически это не вынесено под иф с ошибкой)