Files
http_logger/notifier.go
Vladimir V Maksimov f42570fce2 fix: перевод строки после открывающего ``` — иначе markdown съедает первую строку
В Pachca/Telegram текст сразу после открывающих тройных бэктиков
трактуется как указатель языка code-блока и не отображается. Из-за
этого пропадала первая строка уведомления — [время] METHOD host/URI,
то есть путь и метод запроса не были видны. Добавлен \n после фенса;
тест теперь проверяет перевод строки и наличие URI в выводе.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 09:29:24 +03:00

162 lines
4.0 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"bytes"
"encoding/json"
"fmt"
"html"
"io"
"net"
"net/http"
"net/mail"
"net/smtp"
"strings"
"time"
"gitea.mediatoday.ru/mt/notify"
)
// Notifier — канал отправки уведомлений
type Notifier interface {
Send(data RequestData) error
}
// formatPlain собирает текстовое тело без обёртки.
func formatPlain(d RequestData) string {
var b strings.Builder
fmt.Fprintf(&b, "[%s] %s %s%s\n", d.Time.Format("2006-01-02T15:04:05Z07:00"), d.Method, d.Host, d.URL)
fmt.Fprintf(&b, "RemoteAddr: %s\n", d.RemoteAddr)
b.WriteString("Headers:\n")
for name, values := range d.Header {
for _, v := range values {
fmt.Fprintf(&b, " %s: %s\n", name, v)
}
}
fmt.Fprintf(&b, "Body:\n%s\n", d.Body)
b.WriteString("----\n")
return b.String()
}
// formatMarkdown — обёртка в код-блок для Telegram/Pachca.
func formatMarkdown(d RequestData) string {
return "```\n" + formatPlain(d) + "```"
}
// formatHTML — обёртка в <pre> с экранированием для Email.
func formatHTML(d RequestData) string {
return "<pre>" + html.EscapeString(formatPlain(d)) + "</pre>"
}
// --- Telegram ---
type telegramNotifier struct {
bot *notify.Bot
chatID int64
}
func (t *telegramNotifier) Send(d RequestData) error {
_, err := t.bot.SendTextMessage(t.chatID, formatMarkdown(d))
return err
}
// --- Pachca (входящий вебхук) ---
type pachcaNotifier struct {
webhookURL string
client *http.Client
}
func (p *pachcaNotifier) Send(d RequestData) error {
payload, err := json.Marshal(map[string]string{"message": formatMarkdown(d)})
if err != nil {
return err
}
resp, err := p.client.Post(p.webhookURL, "application/json", bytes.NewReader(payload))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("pachca webhook: HTTP %d: %s", resp.StatusCode, string(body))
}
return nil
}
// --- Email ---
type emailNotifier struct {
auth notify.SmtpAuth
from mail.Address
to []mail.Address
subject string
}
func (e *emailNotifier) Send(d RequestData) error {
return notify.SendEmailHTML(e.auth, formatHTML(d), e.subject, e.from, e.to...)
}
// BuildNotifiers собирает map каналов из конфига. Добавляет только заданные секции.
func BuildNotifiers(cfg *Config) (map[string]Notifier, error) {
notifiers := map[string]Notifier{}
if cfg.Telegram != nil {
disableIPV6 := true
if cfg.Telegram.DisableIPV6 != nil {
disableIPV6 = *cfg.Telegram.DisableIPV6
}
bot, err := notify.NewTelegram(cfg.Telegram.Token, disableIPV6)
if err != nil {
return nil, fmt.Errorf("telegram: %w", err)
}
notifiers["telegram"] = &telegramNotifier{bot: bot, chatID: cfg.Telegram.GroupID}
}
if cfg.Pachca != nil {
if cfg.Pachca.WebhookURL == "" {
return nil, fmt.Errorf("pachca: не задан webhook_url")
}
notifiers["pachca"] = &pachcaNotifier{
webhookURL: cfg.Pachca.WebhookURL,
client: &http.Client{Timeout: 30 * time.Second},
}
}
if cfg.Email != nil {
host, _, err := net.SplitHostPort(cfg.Email.SMTPAddr)
if err != nil {
return nil, fmt.Errorf("email smtp_addr: %w", err)
}
from, err := mail.ParseAddress(cfg.Email.From)
if err != nil {
return nil, fmt.Errorf("email from: %w", err)
}
var to []mail.Address
for _, addr := range cfg.Email.To {
a, err := mail.ParseAddress(addr)
if err != nil {
return nil, fmt.Errorf("email to %q: %w", addr, err)
}
to = append(to, *a)
}
if len(to) == 0 {
return nil, fmt.Errorf("email: не задан ни один получатель (to)")
}
subject := cfg.Email.Subject
if subject == "" {
subject = "HTTP Logger"
}
notifiers["email"] = &emailNotifier{
auth: notify.SmtpAuth{
Addr: cfg.Email.SMTPAddr,
Auth: smtp.PlainAuth("", cfg.Email.Username, cfg.Email.Password, host),
},
from: *from,
to: to,
subject: subject,
}
}
return notifiers, nil
}