mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-06-06 05:51:19 +08:00
- 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>
159 lines
3.5 KiB
Go
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()
|
||
}
|
||
}
|