package logger import ( "fmt" "io" "log" "os" "path/filepath" "strings" "sync" "sync/atomic" "time" "douyu_go/internal/constants" ) // AsyncLogWriter 异步日志写入器,队列满时丢弃日志以保护主链路。 type AsyncLogWriter struct { dst io.Writer ch chan []byte wg sync.WaitGroup closed uint32 dropped uint64 } // NewAsyncLogWriter 创建异步日志写入器。 func NewAsyncLogWriter(dst io.Writer, buffer int) *AsyncLogWriter { if buffer <= 0 { buffer = 1024 } w := &AsyncLogWriter{ dst: dst, ch: make(chan []byte, buffer), } w.wg.Add(1) go w.run() return w } // Write 异步写日志;队列满时丢弃,避免抢购请求被磁盘 I/O 阻塞。 func (w *AsyncLogWriter) Write(p []byte) (int, error) { if w == nil { return len(p), nil } if atomic.LoadUint32(&w.closed) == 1 { return len(p), nil } buf := append([]byte(nil), p...) select { case w.ch <- buf: default: atomic.AddUint64(&w.dropped, 1) } return len(p), nil } func (w *AsyncLogWriter) run() { defer w.wg.Done() for p := range w.ch { _, _ = w.dst.Write(p) } if dropped := atomic.LoadUint64(&w.dropped); dropped > 0 { _, _ = fmt.Fprintf(w.dst, "%s [日志][warn] 日志队列已丢弃 %d 条,已优先保护抢购链路\n", time.Now().In(constants.CST).Format("2006/01/02 15:04:05.000000"), dropped) } } // Close 关闭写入器,等待队列清空。 func (w *AsyncLogWriter) Close() error { if w == nil { return nil } if atomic.CompareAndSwapUint32(&w.closed, 0, 1) { close(w.ch) w.wg.Wait() } return nil } // InitLogging 初始化日志输出:终端、日志文件、内存缓冲三处同时写入。 func InitLogging(store *MemoryLogStore, logDir string) func() { if logDir == "" { logDir = constants.DefaultLogDir } if err := os.MkdirAll(logDir, 0755); err != nil { async := NewAsyncLogWriter(io.MultiWriter(os.Stdout, store), 2048) log.SetOutput(async) log.Printf("日志目录创建失败,仅写入终端和内存: %v", err) return func() { _ = async.Close() } } filePath := filepath.Join(logDir, "douyu_"+time.Now().In(constants.CST).Format("20060102_150405")+".log") file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { async := NewAsyncLogWriter(io.MultiWriter(os.Stdout, store), 2048) log.SetOutput(async) log.Printf("日志文件创建失败,仅写入终端和内存: %v", err) return func() { _ = async.Close() } } store.SetFilePath(filePath) async := NewAsyncLogWriter(io.MultiWriter(os.Stdout, file, store), 2048) log.SetOutput(async) log.Printf("日志文件已启用: %s", filePath) return func() { _ = async.Close() _ = file.Close() } } // LogEvent 输出带分类和级别的结构化文本日志。 func LogEvent(category, level, format string, args ...any) { if category == "" { category = "系统" } if level == "" { level = "info" } log.Printf("[%s][%s] %s", category, level, fmt.Sprintf(format, args...)) } // ParseLogMeta 从标准日志行中提取类似 [网络][warn] 的分类和级别。 func ParseLogMeta(line string) (string, string) { idx := strings.Index(line, "[") if idx < 0 { return "", "" } category, rest, ok := cutBracket(line[idx:]) if !ok { return "", "" } level, _, ok := cutBracket(rest) if !ok || !IsLogLevel(level) { return category, "" } return category, level } func cutBracket(s string) (string, string, bool) { if !strings.HasPrefix(s, "[") { return "", s, false } end := strings.Index(s, "]") if end <= 1 { return "", s, false } return s[1:end], s[end+1:], true } // IsLogLevel 判断是否为有效日志级别。 func IsLogLevel(level string) bool { switch strings.ToLower(level) { case "success", "error", "warn", "info": return true default: return false } } // DetectLogLevel 根据日志文本粗略分类,方便前端染色。 func DetectLogLevel(line string) string { lower := strings.ToLower(line) switch { case strings.Contains(lower, "[success]"): return "success" case strings.Contains(lower, "[error]"): return "error" case strings.Contains(lower, "[warn]"): return "warn" case strings.Contains(lower, "[info]"): return "info" case strings.Contains(line, "成功"): return "success" case strings.Contains(line, "失败") || strings.Contains(line, "错误") || strings.Contains(lower, "error") || strings.Contains(lower, "panic"): return "error" case strings.Contains(line, "降级") || strings.Contains(line, "跳过") || strings.Contains(line, "不存在") || strings.Contains(lower, "warn"): return "warn" default: return "info" } }