package tunnel import ( "container/ring" "fmt" "os" "path/filepath" "sync" "time" "github.com/gotunnel/pkg/protocol" ) const ( maxBufferSize = 1000 // 环形缓冲区最大条目数 logFilePattern = "client.%s.log" // 日志文件名模式 ) // LogLevel 日志级别 type LogLevel int const ( LevelDebug LogLevel = iota LevelInfo LevelWarn LevelError ) // Logger 客户端日志收集器 type Logger struct { dataDir string buffer *ring.Ring bufferMu sync.RWMutex file *os.File fileMu sync.Mutex fileDate string subscribers map[string]chan protocol.LogEntry subMu sync.RWMutex } // NewLogger 创建新的日志收集器 func NewLogger(dataDir string) (*Logger, error) { l := &Logger{ dataDir: dataDir, buffer: ring.New(maxBufferSize), subscribers: make(map[string]chan protocol.LogEntry), } // 确保日志目录存在 logDir := filepath.Join(dataDir, "logs") if err := os.MkdirAll(logDir, 0755); err != nil { return nil, err } return l, nil } // Printf 记录日志 (兼容 log.Printf) func (l *Logger) Printf(format string, args ...interface{}) { l.log(LevelInfo, "client", format, args...) } // Infof 记录信息日志 func (l *Logger) Infof(format string, args ...interface{}) { l.log(LevelInfo, "client", format, args...) } // Warnf 记录警告日志 func (l *Logger) Warnf(format string, args ...interface{}) { l.log(LevelWarn, "client", format, args...) } // Errorf 记录错误日志 func (l *Logger) Errorf(format string, args ...interface{}) { l.log(LevelError, "client", format, args...) } // Debugf 记录调试日志 func (l *Logger) Debugf(format string, args ...interface{}) { l.log(LevelDebug, "client", format, args...) } // PluginLog 记录插件日志 func (l *Logger) PluginLog(pluginName, level, format string, args ...interface{}) { var lvl LogLevel switch level { case "debug": lvl = LevelDebug case "warn": lvl = LevelWarn case "error": lvl = LevelError default: lvl = LevelInfo } l.log(lvl, "plugin:"+pluginName, format, args...) } func (l *Logger) log(level LogLevel, source, format string, args ...interface{}) { entry := protocol.LogEntry{ Timestamp: time.Now().UnixMilli(), Level: levelToString(level), Message: fmt.Sprintf(format, args...), Source: source, } // 注意:不在这里输出到标准输出,因为调用方(logf/logErrorf/logWarnf)已经调用了 log.Print // 这里只负责:缓冲区存储、文件写入、订阅者通知 // 添加到环形缓冲区 l.bufferMu.Lock() l.buffer.Value = entry l.buffer = l.buffer.Next() l.bufferMu.Unlock() // 写入文件 l.writeToFile(entry) // 通知订阅者 l.notifySubscribers(entry) } // Subscribe 订阅日志流 func (l *Logger) Subscribe(sessionID string) <-chan protocol.LogEntry { ch := make(chan protocol.LogEntry, 100) l.subMu.Lock() l.subscribers[sessionID] = ch l.subMu.Unlock() return ch } // Unsubscribe 取消订阅 func (l *Logger) Unsubscribe(sessionID string) { l.subMu.Lock() if ch, ok := l.subscribers[sessionID]; ok { close(ch) delete(l.subscribers, sessionID) } l.subMu.Unlock() } // GetRecentLogs 获取最近的日志 func (l *Logger) GetRecentLogs(lines int, level string) []protocol.LogEntry { l.bufferMu.RLock() defer l.bufferMu.RUnlock() var entries []protocol.LogEntry l.buffer.Do(func(v interface{}) { if v == nil { return } entry := v.(protocol.LogEntry) // 应用级别过滤 if level != "" && entry.Level != level { return } entries = append(entries, entry) }) // 如果指定了行数,返回最后 N 行 if lines > 0 && len(entries) > lines { entries = entries[len(entries)-lines:] } return entries } // Close 关闭日志收集器 func (l *Logger) Close() { l.fileMu.Lock() if l.file != nil { l.file.Close() l.file = nil } l.fileMu.Unlock() l.subMu.Lock() for _, ch := range l.subscribers { close(ch) } l.subscribers = make(map[string]chan protocol.LogEntry) l.subMu.Unlock() } func (l *Logger) writeToFile(entry protocol.LogEntry) { l.fileMu.Lock() defer l.fileMu.Unlock() today := time.Now().Format("2006-01-02") if l.fileDate != today { if l.file != nil { l.file.Close() } logPath := filepath.Join(l.dataDir, "logs", fmt.Sprintf(logFilePattern, today)) f, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) if err != nil { return } l.file = f l.fileDate = today } if l.file != nil { fmt.Fprintf(l.file, "%d|%s|%s|%s\n", entry.Timestamp, entry.Level, entry.Source, entry.Message) } } func (l *Logger) notifySubscribers(entry protocol.LogEntry) { l.subMu.RLock() defer l.subMu.RUnlock() for _, ch := range l.subscribers { select { case ch <- entry: default: // 订阅者太慢,丢弃日志 } } } func levelToString(level LogLevel) string { switch level { case LevelDebug: return "debug" case LevelInfo: return "info" case LevelWarn: return "warn" case LevelError: return "error" default: return "info" } }