Files
nofx/logger/telegram_hook.go
T
icy 7d58f56e49 feat: implement hybrid database architecture and frontend encryption
- Add PostgreSQL + SQLite hybrid database support with automatic switching
- Implement frontend AES-GCM + RSA-OAEP encryption for sensitive data
- Add comprehensive DatabaseInterface with all required methods
- Fix compilation issues with interface consistency
- Update all database method signatures to use DatabaseInterface
- Add missing UpdateTraderInitialBalance method to PostgreSQL implementation
- Integrate RSA public key distribution via /api/config endpoint
- Add frontend crypto service with proper error handling
- Support graceful degradation between encrypted and plaintext transmission
- Add directory creation for RSA keys and PEM parsing fixes
- Test both SQLite and PostgreSQL modes successfully
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
2025-11-06 01:50:06 +08:00

159 lines
3.5 KiB
Go

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()
}
}