telegram: сервис поднимается без бота и работает одним входом
- Клиент бота собирается один раз и достаётся отправителю и транспорту; разрез прошёл по «ответил ли Telegram»: ответ «такого бота нет» роняет старт, недоступность даёт подъём без Telegram (ADR-2026-08-13). Ожидание при сборке ограничено сроком — иначе молчащий Telegram вешал подъём. - Недоставленный ответ не роняет шаг: пишется с job_id и считается метрикой, уровень по причине — WARN для неподнятого входа, ERROR для неназванного адресата. Заведены transcriber_intake_up и transcriber_undelivered_reply_count. - Закрыта утечка токена в журнал: отказ разбора адреса рождается раньше обращения к клиенту, то есть мимо чистки на его границе.
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
|
||||
"git.vakhrushev.me/av/transcriber/internal/adapter/telegram"
|
||||
"git.vakhrushev.me/av/transcriber/internal/contract"
|
||||
"git.vakhrushev.me/av/transcriber/internal/metrics"
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
)
|
||||
|
||||
// buildTelegram заводит вход Telegram при старте. Клиент собирается **один
|
||||
// раз** и достаётся обоим — отправителю ответов и транспорту бота. Пока его
|
||||
// строили порознь, два пути одного старта разошлись: один ронял процесс на
|
||||
// негодном токене, другой терпел, и согласовывать их приходилось руками.
|
||||
func buildTelegram(botToken string, logger *slog.Logger) (*tgbotapi.BotAPI, contract.TelegramMessageSender, error) {
|
||||
bot, err := telegram.NewBot(botToken, logger)
|
||||
return telegramFromBot(bot, err, logger)
|
||||
}
|
||||
|
||||
// telegramFromBot решает, чем обернулась сборка клиента, и это решение —
|
||||
// единственное содержательное здесь. Оно отделено от самого обращения к
|
||||
// Telegram намеренно: обращение ходит в сеть и в проверке недоступно, а
|
||||
// разрез проверять надо, иначе его молча вернут к прежнему виду.
|
||||
//
|
||||
// Разрез проходит по тому, **ответил ли Telegram**, и это решение владельца
|
||||
// от 2026-08-13: недоступность Telegram на старт сервиса не влияет.
|
||||
//
|
||||
// - токен не задан — законный отказ от входа: сервис работает оставшимся;
|
||||
// - Telegram ответил отказом — ошибка настройки: бота по этому токену не
|
||||
// существует, ждать нечего, и старт роняется. Молча потерянный бот
|
||||
// перестаёт отвечать отправителям, а узнать об этом было бы неоткуда;
|
||||
// - до Telegram не дошли — недоступность: сеть, DNS, авария Bot API,
|
||||
// истёкший срок ожидания. Сервис поднимается без Telegram, потому что
|
||||
// основной вход у него другой, и класть его из-за чужой аварии нельзя.
|
||||
//
|
||||
// Ядро во всех трёх случаях получает непустого отправителя: необязательная
|
||||
// зависимость, доехавшая до него нулём, уронила бы первую же задачу из
|
||||
// Telegram.
|
||||
func telegramFromBot(
|
||||
bot *tgbotapi.BotAPI,
|
||||
err error,
|
||||
logger *slog.Logger,
|
||||
) (*tgbotapi.BotAPI, contract.TelegramMessageSender, error) {
|
||||
var apiErr *tgbotapi.Error
|
||||
|
||||
switch {
|
||||
case errors.Is(err, telegram.ErrEmptyToken):
|
||||
metrics.IntakeUpGauge.WithLabelValues("telegram").Set(0)
|
||||
logger.Warn("Telegram bot is not started", "reason", "bot token is not set in configuration")
|
||||
return nil, telegram.NewAbsentMessageSender(), nil
|
||||
|
||||
case errors.As(err, &apiErr):
|
||||
// Отказ токена не несёт: его чистит единая точка `telegram.NewBot`.
|
||||
return nil, nil, err
|
||||
|
||||
case err != nil:
|
||||
metrics.IntakeUpGauge.WithLabelValues("telegram").Set(0)
|
||||
logger.Warn("Telegram bot is not started", "reason", "telegram is unreachable", "error", err)
|
||||
return nil, telegram.NewAbsentMessageSender(), nil
|
||||
|
||||
default:
|
||||
metrics.IntakeUpGauge.WithLabelValues("telegram").Set(1)
|
||||
return bot, telegram.NewTelegramMessageSender(bot, logger), nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user