docs: спецификация настраиваемых каналов уведомлений (Telegram/Pachca/Email)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
# Настраиваемые каналы уведомлений
|
||||
|
||||
Дата: 2026-06-14
|
||||
|
||||
## Цель
|
||||
|
||||
Сейчас `http_logger` умеет отправлять входящие HTTP-запросы только в Telegram —
|
||||
канал жёстко зашит в `main.go`. Нужно дать возможность настраивать каналы
|
||||
получения уведомлений через конфиг и добавить два новых: **Pachca (Пачка)** и
|
||||
**Email**, используя библиотеку `gitea.mediatoday.ru/mt/notify`.
|
||||
|
||||
## Решения (согласованы с пользователем)
|
||||
|
||||
1. **Путь = канал.** Первый сегмент пути URL выбирает канал: `/telegram`,
|
||||
`/pachca`, `/email`. Каждый путь шлёт ровно в свой канал. Сохраняет текущую
|
||||
логику маршрутизации.
|
||||
2. **Один экземпляр на тип.** В конфиге по одной секции на каждый тип канала.
|
||||
3. **Форматирование под каждый канал.** Извлечение данных запроса — общее,
|
||||
форматирование — на стороне канала (Telegram — markdown-блок, Email — HTML).
|
||||
4. **Канал не сконфигурирован → `404`.**
|
||||
5. Поднять модуль `notify` на актуальную версию по новому пути
|
||||
`gitea.mediatoday.ru/mt/notify` (в старой версии нет Pachca/Email).
|
||||
|
||||
## Архитектура
|
||||
|
||||
### Структура данных запроса
|
||||
|
||||
`BuildMessage` разделяется на две части: извлечение данных и форматирование.
|
||||
|
||||
```go
|
||||
// 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"`) сохраняются.
|
||||
|
||||
### Интерфейс канала
|
||||
|
||||
```go
|
||||
// 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`.
|
||||
|
||||
```yaml
|
||||
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:
|
||||
|
||||
```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` — исправляются в рамках работы.
|
||||
|
||||
### Сборка каналов на старте
|
||||
|
||||
```go
|
||||
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`.
|
||||
|
||||
### Маршрутизация в хендлере
|
||||
|
||||
```go
|
||||
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 «один запрос → все каналы».
|
||||
- Несколько экземпляров одного типа канала.
|
||||
- Именованные маршруты с произвольным набором каналов.
|
||||
- Вложения/фото (библиотека умеет, но текущая задача — текст запроса).
|
||||
Reference in New Issue
Block a user