349 lines
7.3 KiB
Go
349 lines
7.3 KiB
Go
package logger
|
||
|
||
import (
|
||
"fmt"
|
||
"strconv"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
|
||
"codex-pool/internal/database"
|
||
)
|
||
|
||
// LogEntry 日志条目
|
||
type LogEntry struct {
|
||
Timestamp time.Time `json:"timestamp"`
|
||
Level string `json:"level"`
|
||
Message string `json:"message"`
|
||
Email string `json:"email,omitempty"`
|
||
Module string `json:"module,omitempty"`
|
||
}
|
||
|
||
// 日志存储(内存缓存 + 数据库持久化)
|
||
var (
|
||
logs = make([]LogEntry, 0, 1000)
|
||
logsMu sync.RWMutex
|
||
listeners = make(map[string]chan LogEntry)
|
||
listMu sync.RWMutex
|
||
)
|
||
|
||
// 旋转动画字符
|
||
var spinnerFrames = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
|
||
var spinnerIndex int
|
||
var spinnerMu sync.Mutex
|
||
|
||
// getSpinnerChar 获取下一个旋转字符
|
||
func getSpinnerChar() string {
|
||
spinnerMu.Lock()
|
||
defer spinnerMu.Unlock()
|
||
char := spinnerFrames[spinnerIndex%len(spinnerFrames)]
|
||
spinnerIndex++
|
||
return char
|
||
}
|
||
|
||
// AddListener 添加日志监听器
|
||
func AddListener(id string) chan LogEntry {
|
||
listMu.Lock()
|
||
defer listMu.Unlock()
|
||
ch := make(chan LogEntry, 100)
|
||
listeners[id] = ch
|
||
return ch
|
||
}
|
||
|
||
// RemoveListener 移除日志监听器
|
||
func RemoveListener(id string) {
|
||
listMu.Lock()
|
||
defer listMu.Unlock()
|
||
if ch, ok := listeners[id]; ok {
|
||
close(ch)
|
||
delete(listeners, id)
|
||
}
|
||
}
|
||
|
||
// broadcast 广播日志
|
||
func broadcast(entry LogEntry) {
|
||
listMu.RLock()
|
||
defer listMu.RUnlock()
|
||
for _, ch := range listeners {
|
||
select {
|
||
case ch <- entry:
|
||
default:
|
||
}
|
||
}
|
||
}
|
||
|
||
// log 记录日志
|
||
func log(level, message, email, module string) {
|
||
entry := LogEntry{
|
||
Timestamp: time.Now(),
|
||
Level: level,
|
||
Message: message,
|
||
Email: email,
|
||
Module: module,
|
||
}
|
||
|
||
// 1. 内存缓存
|
||
logsMu.Lock()
|
||
if len(logs) >= 1000 {
|
||
logs = logs[100:]
|
||
}
|
||
logs = append(logs, entry)
|
||
logsMu.Unlock()
|
||
|
||
// 2. 广播给监听器
|
||
broadcast(entry)
|
||
|
||
// 3. 持久化到数据库(异步,避免阻塞)
|
||
go func() {
|
||
if database.Instance != nil {
|
||
database.Instance.InsertLog(entry.Timestamp, level, message, email, module)
|
||
}
|
||
}()
|
||
|
||
// 打印到控制台 (带时间戳和颜色)
|
||
timestamp := entry.Timestamp.Format("15:04:05")
|
||
|
||
// ANSI 颜色代码
|
||
colorReset := "\033[0m"
|
||
colorGray := "\033[90m"
|
||
colorGreen := "\033[32m"
|
||
colorRed := "\033[31m"
|
||
colorYellow := "\033[33m"
|
||
colorCyan := "\033[36m"
|
||
|
||
// Team 颜色列表(用于区分不同 Team)
|
||
teamColors := []string{
|
||
"\033[38;5;39m", // 亮蓝
|
||
"\033[38;5;208m", // 橙色
|
||
"\033[38;5;141m", // 紫色
|
||
"\033[38;5;48m", // 青绿
|
||
"\033[38;5;197m", // 粉红
|
||
"\033[38;5;226m", // 亮黄
|
||
"\033[38;5;87m", // 青色
|
||
"\033[38;5;156m", // 浅绿
|
||
"\033[38;5;219m", // 浅粉
|
||
"\033[38;5;117m", // 天蓝
|
||
}
|
||
|
||
// 从消息中提取 Team 编号
|
||
teamColor := ""
|
||
if strings.Contains(message, "[Team ") {
|
||
start := strings.Index(message, "[Team ")
|
||
if start >= 0 {
|
||
end := strings.Index(message[start:], "]")
|
||
if end > 0 {
|
||
teamStr := message[start+6 : start+end]
|
||
if teamNum, err := strconv.Atoi(teamStr); err == nil && teamNum > 0 {
|
||
teamColor = teamColors[(teamNum-1)%len(teamColors)]
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
prefix := ""
|
||
color := ""
|
||
switch level {
|
||
case "info":
|
||
prefix = "INFO "
|
||
color = colorCyan
|
||
case "success":
|
||
prefix = "SUCCESS"
|
||
color = colorGreen
|
||
case "error":
|
||
prefix = "ERROR "
|
||
color = colorRed
|
||
case "warning":
|
||
prefix = "WARN "
|
||
color = colorYellow
|
||
case "status":
|
||
// 状态日志使用旋转动画字符
|
||
prefix = getSpinnerChar() + " WAIT "
|
||
color = colorCyan
|
||
}
|
||
|
||
// 模块名固定宽度(8字符)
|
||
moduleStr := module
|
||
if len(moduleStr) < 8 {
|
||
moduleStr = moduleStr + strings.Repeat(" ", 8-len(moduleStr))
|
||
} else if len(moduleStr) > 8 {
|
||
moduleStr = moduleStr[:8]
|
||
}
|
||
|
||
// 如果是 Team 相关日志,消息使用 Team 颜色
|
||
msgColor := colorReset
|
||
if teamColor != "" {
|
||
msgColor = teamColor
|
||
}
|
||
|
||
if email != "" {
|
||
// 截断长邮箱,保持对齐
|
||
emailDisplay := email
|
||
if len(emailDisplay) > 35 {
|
||
emailDisplay = emailDisplay[:32] + "..."
|
||
}
|
||
fmt.Printf("%s%s%s %s[%s]%s [%s] %s%s%s\n",
|
||
colorGray, timestamp, colorReset,
|
||
color, prefix, colorReset,
|
||
moduleStr, msgColor, message, colorReset)
|
||
} else {
|
||
fmt.Printf("%s%s%s %s[%s]%s [%s] %s%s%s\n",
|
||
colorGray, timestamp, colorReset,
|
||
color, prefix, colorReset,
|
||
moduleStr, msgColor, message, colorReset)
|
||
}
|
||
}
|
||
|
||
// Info 记录信息日志
|
||
func Info(message, email, module string) {
|
||
log("info", message, email, module)
|
||
}
|
||
|
||
// Success 记录成功日志
|
||
func Success(message, email, module string) {
|
||
log("success", message, email, module)
|
||
}
|
||
|
||
// Error 记录错误日志
|
||
func Error(message, email, module string) {
|
||
log("error", message, email, module)
|
||
}
|
||
|
||
// Warning 记录警告日志
|
||
func Warning(message, email, module string) {
|
||
log("warning", message, email, module)
|
||
}
|
||
|
||
// Status 记录状态日志(带旋转动画字符,用于等待中的操作)
|
||
func Status(message, email, module string) {
|
||
log("status", message, email, module)
|
||
}
|
||
|
||
// GetLogs 获取日志
|
||
func GetLogs(limit int) []LogEntry {
|
||
logsMu.RLock()
|
||
defer logsMu.RUnlock()
|
||
|
||
if limit <= 0 || limit > len(logs) {
|
||
limit = len(logs)
|
||
}
|
||
|
||
start := len(logs) - limit
|
||
if start < 0 {
|
||
start = 0
|
||
}
|
||
|
||
result := make([]LogEntry, limit)
|
||
copy(result, logs[start:])
|
||
return result
|
||
}
|
||
|
||
// GetLogsByModule 按模块筛选日志并分页(最新的在前)
|
||
func GetLogsByModule(module string, page, pageSize int) ([]LogEntry, int) {
|
||
logsMu.RLock()
|
||
defer logsMu.RUnlock()
|
||
|
||
// 倒序收集匹配的日志
|
||
var filtered []LogEntry
|
||
for i := len(logs) - 1; i >= 0; i-- {
|
||
if logs[i].Module == module {
|
||
filtered = append(filtered, logs[i])
|
||
}
|
||
}
|
||
|
||
total := len(filtered)
|
||
if page < 1 {
|
||
page = 1
|
||
}
|
||
if pageSize <= 0 {
|
||
pageSize = 5
|
||
}
|
||
|
||
start := (page - 1) * pageSize
|
||
if start >= total {
|
||
return []LogEntry{}, total
|
||
}
|
||
end := start + pageSize
|
||
if end > total {
|
||
end = total
|
||
}
|
||
|
||
return filtered[start:end], total
|
||
}
|
||
|
||
// ClearLogs 清空日志(内存 + 数据库)
|
||
func ClearLogs() {
|
||
logsMu.Lock()
|
||
defer logsMu.Unlock()
|
||
logs = make([]LogEntry, 0, 1000)
|
||
|
||
// 同时清空数据库
|
||
if database.Instance != nil {
|
||
database.Instance.ClearLogs()
|
||
}
|
||
}
|
||
|
||
// GetLogsByModuleAndLevel 按模块和级别筛选日志并分页(最新的在前)
|
||
func GetLogsByModuleAndLevel(module, level string, page, pageSize int) ([]LogEntry, int) {
|
||
logsMu.RLock()
|
||
defer logsMu.RUnlock()
|
||
|
||
// 倒序收集匹配的日志
|
||
var filtered []LogEntry
|
||
for i := len(logs) - 1; i >= 0; i-- {
|
||
if logs[i].Module == module {
|
||
// 如果指定了级别,则进行过滤
|
||
if level != "" && logs[i].Level != level {
|
||
continue
|
||
}
|
||
filtered = append(filtered, logs[i])
|
||
}
|
||
}
|
||
|
||
total := len(filtered)
|
||
if page < 1 {
|
||
page = 1
|
||
}
|
||
if pageSize <= 0 {
|
||
pageSize = 5
|
||
}
|
||
|
||
start := (page - 1) * pageSize
|
||
if start >= total {
|
||
return []LogEntry{}, total
|
||
}
|
||
end := start + pageSize
|
||
if end > total {
|
||
end = total
|
||
}
|
||
|
||
return filtered[start:end], total
|
||
}
|
||
|
||
// ClearLogsByModule 按模块清除日志(内存 + 数据库)
|
||
func ClearLogsByModule(module string) int {
|
||
logsMu.Lock()
|
||
defer logsMu.Unlock()
|
||
|
||
// 过滤掉指定模块的日志(内存)
|
||
var newLogs []LogEntry
|
||
cleared := 0
|
||
for _, log := range logs {
|
||
if log.Module != module {
|
||
newLogs = append(newLogs, log)
|
||
} else {
|
||
cleared++
|
||
}
|
||
}
|
||
logs = newLogs
|
||
|
||
// 同时清空数据库
|
||
if database.Instance != nil {
|
||
dbCleared, _ := database.Instance.ClearLogsByModule(module)
|
||
if dbCleared > int64(cleared) {
|
||
cleared = int(dbCleared)
|
||
}
|
||
}
|
||
|
||
return cleared
|
||
}
|