Шаг 1: Создание Telegram-бота Зарегистрируйте бота через BotFather: Откройте Telegram и найдите @BotFather. Отправьте команду /start. Отправьте /newbot, чтобы создать нового бота. Следуйте инструкциям: Укажите имя бота (например, MyLogBot). Укажите username бота, заканчивающийся на bot (например, @MyLogAllBot). После создания вы получите токен (например, 8007457609:AAGnrXOSaoxODMSh4yCzUEIp0uxfH3Hk8ws). Сохраните его, он понадобится для API. # Настройка Vector для отправки логов в Gotify Эта инструкция описывает, как установить Vector, настроить его для чтения JSON-логов из файла `/opt/org_ccalm_main/logs/ccalm.log`, фильтрации строк с уровнем `ERROR` и отправки уведомлений в Gotify. --- ## 0. ✅ Подключаемся к инфраструктуре ```sh ssh ubuntu@almaty.ccalm.org -p 22 ``` ## Предварительные требования - ОС: Ubuntu/Debian (для других ОС уточните, чтобы адаптировать команды). - Файл логов: `/opt/org_ccalm_main/logs/ccalm.log` с JSON-строками (поля `level` и `message`). - Gotify: Доступный сервер Gotify с URL и токеном (например, `https://gotify.example.com/message?token=`). - Доступ к терминалу с правами `sudo`. ## Шаг 1: Установка Vector Пробуем скачать deb пакет с сайта ```sh curl -L https://packages.timber.io/vector/0.46.X/vector_0.46.1-1_amd64.deb -o vector_0.46.1-1_amd64.deb && sudo dpkg -i vector_0.46.1-1_amd64.deb && sudo apt-get install -f && vector --version ``` That make deleting: ```sh sudo apt remove --purge vector ``` ## Шаг 2: Создание конфигурации Vector Vector использует YAML для конфигурации. Настроим чтение логов, фильтрацию `ERROR` и отправку в Gotify. 1. Создайте файл конфигурации `/etc/vector/vector.yaml`: ```sh cd /etc/vector && sudo tee vector.yaml > /dev/null <<'EOF' data_dir: "/var/lib/vector" sources: ccalm_logs: type: file include: - /opt/org_ccalm_main/logs/org-ccalm-main.log read_from: beginning ccalm_translation_logs: type: file include: - /opt/org_ccalm_translation/logs/org-ccalm-translation.log read_from: beginning ccalm_dbms_logs: type: file include: - /opt/org_ccalm_dbms/logs/org-ccalm-dbms.log read_from: beginning ccalm_jwt_logs: type: file include: - /opt/org_ccalm_jwt/logs/org-ccalm-jwt.log read_from: beginning transforms: parse_json_ccalm: type: remap inputs: - ccalm_logs source: | structured, err = parse_json(.message) if err != null { abort } merged, err = merge(., structured) if err != null { abort } . = merged parse_json_translation: type: remap inputs: - ccalm_translation_logs source: | structured, err = parse_json(.message) if err != null { abort } merged, err = merge(., structured) if err != null { abort } . = merged parse_json_dbms: type: remap inputs: - ccalm_dbms_logs source: | structured, err = parse_json(.message) if err != null { abort } merged, err = merge(., structured) if err != null { abort } . = merged parse_json_jwt: type: remap inputs: - ccalm_jwt_logs source: | structured, err = parse_json(.message) if err != null { abort } merged, err = merge(., structured) if err != null { abort } . = merged filter_errors_ccalm: type: filter inputs: - parse_json_ccalm condition: '.level == "ERROR" || .level == "WARN"' filter_errors_translation: type: filter inputs: - parse_json_translation condition: '.level == "ERROR" || .level == "WARN"' filter_errors_dbms: type: filter inputs: - parse_json_dbms condition: '.level == "ERROR" || .level == "WARN"' filter_errors_jwt: type: filter inputs: - parse_json_jwt condition: '.level == "ERROR" || .level == "WARN"' format_telegram_json_ccalm: type: remap inputs: - filter_errors_ccalm source: | msg, err = string(.message) if err != null { msg = "Unable to parse message" } marker_str = "" if exists(.marker) { m, err = string(.marker) if err == null && length(m) > 0 { marker_str = "Marker: ```text\n" + m + "```" } } level_str = "" icon_str = "" if exists(.level) { m, err = string(.level) if err == null { level_str = "level: ```text\n" + m + "```" } if m == "ERROR" { icon_str = "‼" } if m == "WARN" { icon_str = "⚠️" } } .message = "{\"chat_id\":\"-1002640082189\",\"message_thread_id\":2,\"text\":\"" + icon_str + " " + level_str + marker_str + "Message: ```text\n" + msg + "```\", \"parse_mode\":\"Markdown\"}" format_telegram_json_translation: type: remap inputs: - filter_errors_translation source: | msg, err = string(.message) if err != null { msg = "Unable to parse message" } marker_str = "" if exists(.marker) { m, err = string(.marker) if err == null && length(m) > 0 { marker_str = "Marker: ```text\n" + m + "```" } } level_str = "" icon_str = "" if exists(.level) { m, err = string(.level) if err == null { level_str = "level: ```text\n" + m + "```" } if m == "ERROR" { icon_str = "‼" } if m == "WARN" { icon_str = "⚠️" } } .message = "{\"chat_id\":\"-1002640082189\",\"message_thread_id\":24,\"text\":\"" + icon_str + " " + level_str + marker_str + "Message: ```text\n" + msg + "```\", \"parse_mode\":\"Markdown\"}" format_telegram_json_dbms: type: remap inputs: - filter_errors_dbms source: | msg, err = string(.message) if err != null { msg = "Unable to parse message" } marker_str = "" if exists(.marker) { m, err = string(.marker) if err == null && length(m) > 0 { marker_str = "Marker: ```text\n" + m + "```" } } level_str = "" icon_str = "" if exists(.level) { m, err = string(.level) if err == null { level_str = "level: ```text\n" + m + "```" } if m == "ERROR" { icon_str = "‼" } if m == "WARN" { icon_str = "⚠️" } } .message = "{\"chat_id\":\"-1002640082189\",\"message_thread_id\":9,\"text\":\"" + icon_str + " " + level_str + marker_str + "Message: ```text\n" + msg + "```\", \"parse_mode\":\"Markdown\"}" format_telegram_json_jwt: type: remap inputs: - filter_errors_jwt source: | msg, err = string(.message) if err != null { msg = "Unable to parse message" } marker_str = "" if exists(.marker) { m, err = string(.marker) if err == null && length(m) > 0 { marker_str = "Marker: ```text\n" + m + "```" } } level_str = "" icon_str = "" if exists(.level) { m, err = string(.level) if err == null { level_str = "level: ```text\n" + m + "```" } if m == "ERROR" { icon_str = "‼" } if m == "WARN" { icon_str = "⚠️" } } .message = "{\"chat_id\":\"-1002640082189\",\"message_thread_id\":4,\"text\":\"" + icon_str + " " + level_str + marker_str + "Message: ```text\n" + msg + "```\", \"parse_mode\":\"Markdown\"}" sinks: telegram_ccalm: type: http inputs: - format_telegram_json_ccalm uri: "https://api.telegram.org/bot8007457609:AAGnrXOSaoxODMSh4yCzUEIp0uxfH3Hk8ws/sendMessage" method: post encoding: codec: text request: headers: Content-Type: "application/json" batch: max_events: 1 telegram_translation: type: http inputs: - format_telegram_json_translation uri: "https://api.telegram.org/bot8007457609:AAGnrXOSaoxODMSh4yCzUEIp0uxfH3Hk8ws/sendMessage" method: post encoding: codec: text request: headers: Content-Type: "application/json" batch: max_events: 1 telegram_dbms: type: http inputs: - format_telegram_json_dbms uri: "https://api.telegram.org/bot8007457609:AAGnrXOSaoxODMSh4yCzUEIp0uxfH3Hk8ws/sendMessage" method: post encoding: codec: text request: headers: Content-Type: "application/json" batch: max_events: 1 telegram_jwt: type: http inputs: - format_telegram_json_jwt uri: "https://api.telegram.org/bot8007457609:AAGnrXOSaoxODMSh4yCzUEIp0uxfH3Hk8ws/sendMessage" method: post encoding: codec: text request: headers: Content-Type: "application/json" batch: max_events: 1 EOF ``` curl https://api.telegram.org/bot8007457609:AAGnrXOSaoxODMSh4yCzUEIp0uxfH3Hk8ws/getUpdates Пробую отправку через curl ```sh curl -X POST -k "https://gotify.geovizor.top:8443/message?token=AYmcpr43YtPKDmZ" \ -H "Content-Type: application/json" \ -d '{"message": "Test message", "priority": 5}' ``` Проверяем что gotify работает: ```sh curl -k -X POST -H "Content-Type: application/json" -H "Host: gotify.geovizor.top" -d '{"message":"Test message 00","priority":5}' --http1.1 https://gotify.geovizor.top:8443/message?token=AYmcpr43YtPKDmZ -v ``` ### Объяснение конфигурации - **Source (`ccalm_logs`)**: Читает логи из файла, парсит JSON, поддерживает многострочные логи. - **Transform (`filter_errors`)**: Фильтрует логи с `level: "ERROR"`. - **Sink (`gotify`)**: Отправляет отфильтрованные логи в Gotify через HTTP POST. ## Шаг 3: Проверка конфигурации Проверьте корректность YAML: ```sh vector --config /etc/vector/vector.yaml validate ``` Ожидаемый вывод: `Configuration is valid`. ## Шаг 4: Запуск Vector ### Тестовый запуск (для отладки) ```sh sudo vector --config /etc/vector/vector.yaml ``` ### Запуск как сервиса 1. Включите и запустите сервис: ```sh sudo systemctl enable vector ``` ```sh sudo systemctl start vector ``` 2. Проверьте статус: ```sh sudo systemctl status vector ``` ```sh sudo systemctl stop vector ``` ## Шаг 5: Проверка отправки в Gotify 1. Убедитесь, что Gotify доступен по указанному URL. 2. Добавьте тестовую строку лога в `/opt/org_ccalm_main/logs/ccalm.log`: ```sh echo '{"level": "ERROR", "marker":"12345", "message": "Database connection failed 0", "timestamp": "2025-05-18T12:28:00Z"}' | sudo tee -a /opt/org_ccalm_jwt/logs/org-ccalm-jwt.log ``` 3. Проверьте Gotify (веб-интерфейс или приложение) — должно прийти уведомление с заголовком "CCALM Log Error" и сообщением "Database connection failed". ## Шаг 6: Отладка - Логи Vector: `/var/log/vector/vector.log` или stdout (в тестовом режиме). - Проверьте логи при проблемах: ```sh cat /var/log/vector/vector.log ``` ## Дополнительные настройки - **Права доступа к файлу логов**: ```sh sudo chown vector:vector /opt/org_ccalm_main/logs/ccalm.log sudo chmod 644 /opt/org_ccalm_main/logs/ccalm.log ``` - **Если JSON-формат отличается**: Если поле `level` называется иначе (например, `log_level`), замените `.level` на `.log_level` в `condition`. - **HTTP вместо HTTPS**: Если Gotify использует HTTP, замените `https://` на `http://` в `uri`. ## Примечания - Убедитесь, что Gotify настроен и токен действителен. - Если логи не отправляются, проверьте сетевую доступность Gotify и правильность URL/токена. - Для чтения только новых логов удалите `read_from: beginning` в конфигурации.