Files
nofx/logger/telegram_hook.go

159 lines
3.5 KiB
Go
Raw 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 logger
import (
"fmt"
"runtime"
"strings"
"github.com/sirupsen/logrus"
)
// TelegramHook 实现logrus.Hook接口将日志推送到Telegram
type TelegramHook struct {
sender *TelegramSender
levels []logrus.Level
enabled bool
}
// NewTelegramHook 创建Telegram Hook
func NewTelegramHook(config *TelegramConfig) (*TelegramHook, error) {
if !config.Enabled {
return &TelegramHook{enabled: false}, nil
}
if config.BotToken == "" || config.ChatID == 0 {
return nil, fmt.Errorf("telegram配置不完整: bot_token和chat_id不能为空")
}
// 创建发送器(使用默认参数)
sender, err := NewTelegramSender(config.BotToken, config.ChatID)
if err != nil {
return nil, fmt.Errorf("创建telegram发送器失败: %w", err)
}
hook := &TelegramHook{
sender: sender,
levels: config.GetLogrusLevels(),
enabled: true,
}
return hook, nil
}
// Levels 返回需要触发的日志级别
func (h *TelegramHook) Levels() []logrus.Level {
if !h.enabled {
return []logrus.Level{}
}
return h.levels
}
// Fire 当日志触发时调用
func (h *TelegramHook) Fire(entry *logrus.Entry) error {
if !h.enabled {
return nil
}
// 格式化消息
message := h.formatMessage(entry)
// 异步发送(非阻塞)
h.sender.SendAsync(message)
return nil
}
// formatMessage 格式化日志消息为Telegram格式
func (h *TelegramHook) formatMessage(entry *logrus.Entry) string {
// 级别emoji
levelEmoji := h.getLevelEmoji(entry.Level)
// 基本信息
var builder strings.Builder
builder.WriteString(fmt.Sprintf("%s *%s*: 系统日志警报\n", levelEmoji, strings.ToUpper(entry.Level.String())))
builder.WriteString(fmt.Sprintf("📝 消息: `%s`\n", escapeMarkdown(entry.Message)))
// 字段信息
if len(entry.Data) > 0 {
builder.WriteString("📊 字段:\n")
for key, value := range entry.Data {
builder.WriteString(fmt.Sprintf(" • %s: `%v`\n", key, value))
}
}
// 调用位置
if entry.HasCaller() {
file := entry.Caller.File
// 只保留相对路径
if idx := strings.Index(file, "nofx/"); idx >= 0 {
file = file[idx:]
}
builder.WriteString(fmt.Sprintf("📍 位置: `%s:%d`\n", file, entry.Caller.Line))
} else {
// 如果entry没有caller手动获取
if _, file, line, ok := runtime.Caller(8); ok {
if idx := strings.Index(file, "nofx/"); idx >= 0 {
file = file[idx:]
}
builder.WriteString(fmt.Sprintf("📍 位置: `%s:%d`\n", file, line))
}
}
// 时间戳
builder.WriteString(fmt.Sprintf("🕐 时间: `%s`", entry.Time.Format("2006-01-02 15:04:05")))
return builder.String()
}
// getLevelEmoji 获取日志级别对应的emoji
func (h *TelegramHook) getLevelEmoji(level logrus.Level) string {
switch level {
case logrus.PanicLevel:
return "🔴"
case logrus.FatalLevel:
return "🔴"
case logrus.ErrorLevel:
return "🟠"
case logrus.WarnLevel:
return "🟡"
case logrus.InfoLevel:
return "🟢"
case logrus.DebugLevel:
return "🔵"
default:
return "⚪"
}
}
// escapeMarkdown 转义Markdown特殊字符
func escapeMarkdown(text string) string {
replacer := strings.NewReplacer(
"_", "\\_",
"*", "\\*",
"[", "\\[",
"]", "\\]",
"(", "\\(",
")", "\\)",
"~", "\\~",
"`", "\\`",
">", "\\>",
"#", "\\#",
"+", "\\+",
"-", "\\-",
"=", "\\=",
"|", "\\|",
"{", "\\{",
"}", "\\}",
".", "\\.",
"!", "\\!",
)
return replacer.Replace(text)
}
// Stop 停止Hook优雅关闭
func (h *TelegramHook) Stop() {
if h.enabled && h.sender != nil {
h.sender.Stop()
}
}