This commit is contained in:
Magnus Root 2026-03-10 17:38:08 +03:00
parent e1ddd0b656
commit f28ccce6a0

View file

@ -36,6 +36,7 @@ const (
dcKindStatusChanged dcChangeKind = "status_changed"
dcKindHealthChanged dcChangeKind = "health_changed"
dcKindDisappeared dcChangeKind = "disappeared"
dcKindAppeared dcChangeKind = "appeared"
)
type dcStateChange struct {
@ -58,6 +59,10 @@ func (ch dcStateChange) isProblem() bool {
return false
}
func (ch dcStateChange) isNew() bool {
return ch.Kind == dcKindAppeared
}
func (ch dcStateChange) isRecovery() bool {
switch ch.Kind {
case dcKindStatusChanged:
@ -108,6 +113,7 @@ func runDockerCheck() error {
// Загружаем предыдущее состояние
prevState := dcLoadState()
isFirstRun := len(prevState.Containers) == 0
// Строим map текущего состояния
current := make(map[string]dcContainerInfo, len(containers))
@ -116,7 +122,7 @@ func runDockerCheck() error {
}
// Сравниваем
changes := dcCompareStates(prevState.Containers, current)
changes := dcCompareStates(prevState.Containers, current, isFirstRun)
// Статистика
runningCount := 0
@ -140,6 +146,8 @@ func runDockerCheck() error {
for _, ch := range changes {
arrow := dcChangeArrow(ch)
switch ch.Kind {
case dcKindAppeared:
log.Printf(" %s %-30s | появился (%s) | %s", arrow, ch.Name, ch.CurrState.Status, ch.Image)
case dcKindDisappeared:
log.Printf(" %s %-30s | исчез (был: %s)", arrow, ch.Name, ch.PrevState.Status)
case dcKindStatusChanged:
@ -265,7 +273,7 @@ func dcNormalizeStatus(raw string) string {
// ── Сравнение состояний ──────────────────────────────────────────────────────
func dcCompareStates(prev map[string]dcContainerState, curr map[string]dcContainerInfo) []dcStateChange {
func dcCompareStates(prev map[string]dcContainerState, curr map[string]dcContainerInfo, isFirstRun bool) []dcStateChange {
var changes []dcStateChange
// Проверяем текущие контейнеры
@ -273,7 +281,16 @@ func dcCompareStates(prev map[string]dcContainerState, curr map[string]dcContain
prevInfo, existed := prev[name]
if !existed {
// Новый контейнер — не алертим, просто добавится в state
if !isFirstRun {
// Новый контейнер появился после первого запуска — алертим
changes = append(changes, dcStateChange{
Name: name,
Kind: dcKindAppeared,
PrevState: dcContainerState{},
CurrState: dcContainerState{Status: currInfo.Status, Health: currInfo.Health},
Image: currInfo.Image,
})
}
continue
}
@ -365,29 +382,34 @@ func dcSaveState(current map[string]dcContainerInfo) {
// ── Отправка алерта ──────────────────────────────────────────────────────────
func dcSendAlert(hostname string, changes []dcStateChange) {
var problems, recoveries []dcStateChange
var problems, recoveries, appeared []dcStateChange
for _, ch := range changes {
if ch.isRecovery() {
switch {
case ch.isNew():
appeared = append(appeared, ch)
case ch.isRecovery():
recoveries = append(recoveries, ch)
} else if ch.isProblem() {
case ch.isProblem():
problems = append(problems, ch)
}
}
// Если нет ни проблем, ни восстановлений — молчим
if len(problems) == 0 && len(recoveries) == 0 {
// Если нет никаких изменений — молчим
if len(problems) == 0 && len(recoveries) == 0 && len(appeared) == 0 {
return
}
// Заголовок по наиболее критичному событию
var header string
switch {
case len(problems) > 0 && len(recoveries) > 0:
header = "⚠️ <b>Изменения в состоянии контейнеров</b>"
case len(problems) > 0:
header = "🚨 <b>АЛЕРТ: Проблемы с контейнерами!</b>"
default:
case len(recoveries) > 0 && len(appeared) > 0:
header = "⚠️ <b>Изменения в состоянии контейнеров</b>"
case len(recoveries) > 0:
header = "✅ <b>Контейнеры восстановлены</b>"
default:
header = " <b>Новые контейнеры</b>"
}
message := fmt.Sprintf(
@ -458,6 +480,20 @@ func dcSendAlert(hostname string, changes []dcStateChange) {
}
}
// Новые контейнеры
if len(appeared) > 0 {
message += "━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
message += fmt.Sprintf("🆕 <b>Появились (%d):</b>\n\n", len(appeared))
for _, ch := range appeared {
message += fmt.Sprintf(
"🆕 <b>%s</b>\n"+
" 📌 Статус: <code>%s</code>\n"+
" 🐳 <code>%s</code>\n\n",
ch.Name, ch.CurrState.Status, ch.Image,
)
}
}
message += "━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
message += "💡 <i>docker ps -a | docker logs &lt;имя&gt; --tail 50</i>"
@ -492,6 +528,8 @@ func dcStatusEmoji(status, health string) string {
func dcChangeEmoji(ch dcStateChange) string {
switch ch.Kind {
case dcKindAppeared:
return "🆕"
case dcKindDisappeared:
return "👻"
case dcKindHealthChanged:
@ -510,6 +548,9 @@ func dcChangeEmoji(ch dcStateChange) string {
}
func dcChangeArrow(ch dcStateChange) string {
if ch.isNew() {
return "🆕"
}
if ch.isRecovery() {
return "✅"
}