8.6 KiB
Настраиваемые каналы уведомлений
Дата: 2026-06-14
Цель
Сейчас http_logger умеет отправлять входящие HTTP-запросы только в Telegram —
канал жёстко зашит в main.go. Нужно дать возможность настраивать каналы
получения уведомлений через конфиг и добавить два новых: Pachca (Пачка) и
Email, используя библиотеку gitea.mediatoday.ru/mt/notify.
Решения (согласованы с пользователем)
- Путь = канал. Первый сегмент пути URL выбирает канал:
/telegram,/pachca,/email. Каждый путь шлёт ровно в свой канал. Сохраняет текущую логику маршрутизации. - Один экземпляр на тип. В конфиге по одной секции на каждый тип канала.
- Форматирование под каждый канал. Извлечение данных запроса — общее, форматирование — на стороне канала (Telegram — markdown-блок, Email — HTML).
- Канал не сконфигурирован →
404. - Поднять модуль
notifyна актуальную версию по новому путиgitea.mediatoday.ru/mt/notify(в старой версии нет Pachca/Email).
Архитектура
Структура данных запроса
BuildMessage разделяется на две части: извлечение данных и форматирование.
// RequestData — извлечённые данные входящего запроса
type RequestData struct {
Time time.Time
Method string
Host string
URL string
RemoteAddr string
Header http.Header
Body string
}
// ExtractRequest читает запрос один раз (тело с лимитом 1024 байт, как сейчас)
func ExtractRequest(r *http.Request) RequestData
Логика GetRemoteAddr и лимит тела (> 1024 → "too long") сохраняются.
Интерфейс канала
// Notifier — канал отправки уведомлений
type Notifier interface {
Send(data RequestData) error
}
Реализации
Каждая реализация сама форматирует RequestData в свой формат.
telegramNotifier{bot *notify.Bot, chatID int64}Форматирует тело в```-блок (как сейчас), отправляетbot.SendTextMessage(ParseMode Markdown).pachcaNotifier{client *notify.Pachca, chatID int64}Форматирует в markdown```-блок, отправляетclient.SendMessage(chatID, text).emailNotifier{auth notify.SmtpAuth, from mail.Address, to []mail.Address, subject string}Форматирует тело в<pre>…</pre>с HTML-экранированием содержимого, отправляетnotify.SendEmailHTML(auth, body, subject, from, to...).
Каждая реализация выносится в отдельный файл (telegram.go, pachca.go,
email.go) либо группируется в notifiers.go — детали на этапе плана. Цель —
не раздувать main.go.
Конфиг
Любую секцию можно опустить — тогда соответствующий путь даёт 404.
listen_addresses:
- ":8080"
- "127.0.0.1:9090"
log_dir: "logs"
telegram:
token: "*****"
group_id: -10012345
disable_ipv6: true # опционально, по умолчанию true
pachca:
token: "*****"
chat_id: 12345
email:
smtp_addr: "smtp.example.com:587"
username: "user@example.com"
password: "*****"
from: "logger@example.com"
to:
- "alert@example.com"
subject: "HTTP Logger" # опционально, по умолчанию "HTTP Logger"
Структуры Go:
type Config struct {
ListenAddresses []string `yaml:"listen_addresses"`
LogDir string `yaml:"log_dir"`
Telegram *ConfigTelegram `yaml:"telegram"`
Pachca *ConfigPachca `yaml:"pachca"`
Email *ConfigEmail `yaml:"email"`
}
type ConfigTelegram struct {
Token string `yaml:"token"`
GroupID int64 `yaml:"group_id"`
DisableIPV6 *bool `yaml:"disable_ipv6"` // nil → true
}
type ConfigPachca struct {
Token string `yaml:"token"`
ChatID int64 `yaml:"chat_id"`
}
type ConfigEmail struct {
SMTPAddr string `yaml:"smtp_addr"`
Username string `yaml:"username"`
Password string `yaml:"password"`
From string `yaml:"from"`
To []string `yaml:"to"`
Subject string `yaml:"subject"` // "" → "HTTP Logger"
}
Замечание: исходный конфиг содержал опечатку token:"*****" (без пробела) и
имя типа ConfigTelegraam — исправляются в рамках работы.
Сборка каналов на старте
notifiers := map[string]Notifier{}
- Для каждой заданной секции конфига строим соответствующий
Notifierи кладём в map под ключом-именем пути ("telegram","pachca","email"). - Для email: хост для
smtp.PlainAuthизвлекается изsmtp_addrчерезnet.SplitHostPort;auth = smtp.PlainAuth("", username, password, host). - Если ни одна секция не задана →
log.Fatal("не сконфигурирован ни один канал"). - Ошибка инициализации конкретного канала (например,
NewTelegram) →log.Fatal.
Маршрутизация в хендлере
paths := strings.Split(r.URL.Path, "/") // "/telegram" → ["", "telegram"]
if len(paths) < 2 { 404 }
n, ok := notifiers[paths[1]]
if !ok { 404 } // неизвестный или несконфигурированный канал
data := ExtractRequest(r)
if err := n.Send(data); err != nil {
log.Println(err); 502 + err.Error()
} else {
200 + "OK"
}
Поведение кодов ответа (404 / 502 / 200) сохраняется как в текущей версии.
Обработка ошибок
- Несконфигурированный/неизвестный путь →
404. - Ошибка отправки в канал →
502+ текст ошибки в теле (как сейчас). - Ошибки конфигурации/инициализации каналов →
log.Fatalна старте.
Зависимости
go.mod: заменитьgit.gm6.ru/icewind/notifyнаgitea.mediatoday.ru/mt/notify v0.0.0-20260405185738-6f5db00fcb34(доступна в module cache). Обновитьgo.sumчерезgo mod tidy.- Новые импорты:
net/mail,net/smtp,html(для экранирования HTML email).
Тестирование
ExtractRequest— модульный тест: корректный разбор метода/хоста/URL/заголовков,GetRemoteAddr(X-Forwarded-For → X-Real-IP → RemoteAddr), лимит тела> 1024.- Форматирование каждого канала — тест, проверяющий обёртку (
```-блок для Telegram/Pachca,<pre>+ экранирование для Email). - Маршрутизация хендлера — тест через
httptest: неизвестный путь →404, известный канал с мокомNotifier→200, ошибкаSend→502. ИнтерфейсNotifierпозволяет подменить реальные каналы моком. - Реальная отправка в Telegram/Pachca/Email не тестируется (внешние сервисы).
Вне рамок (YAGNI)
- Broadcast «один запрос → все каналы».
- Несколько экземпляров одного типа канала.
- Именованные маршруты с произвольным набором каналов.
- Вложения/фото (библиотека умеет, но текущая задача — текст запроса).