package monitor import ( "fmt" "log" "strings" "sync" "time" ) type LogEntry struct { Time string `json:"time"` Level string `json:"level"` Message string `json:"message"` } type logHub struct { mutex sync.RWMutex entries []LogEntry maxEntries int subscribers map[chan LogEntry]struct{} } var hub = &logHub{ maxEntries: 1000, subscribers: make(map[chan LogEntry]struct{}), } func Init(maxEntries int) { if maxEntries > 0 { hub.maxEntries = maxEntries } } func Infof(format string, args ...interface{}) { appendEntry("info", fmt.Sprintf(format, args...)) } func Warnf(format string, args ...interface{}) { appendEntry("warn", fmt.Sprintf(format, args...)) } func Errorf(format string, args ...interface{}) { appendEntry("error", fmt.Sprintf(format, args...)) } func Auditf(format string, args ...interface{}) { appendEntry("audit", fmt.Sprintf(format, args...)) } func appendEntry(level, message string) { entry := LogEntry{ Time: time.Now().Format(time.RFC3339), Level: strings.ToLower(level), Message: message, } log.Printf("[%s] %s", strings.ToUpper(entry.Level), entry.Message) hub.mutex.Lock() hub.entries = append(hub.entries, entry) if len(hub.entries) > hub.maxEntries { hub.entries = hub.entries[len(hub.entries)-hub.maxEntries:] } for ch := range hub.subscribers { select { case ch <- entry: default: } } hub.mutex.Unlock() } func Subscribe() chan LogEntry { ch := make(chan LogEntry, 100) hub.mutex.Lock() hub.subscribers[ch] = struct{}{} hub.mutex.Unlock() return ch } func Unsubscribe(ch chan LogEntry) { hub.mutex.Lock() if _, ok := hub.subscribers[ch]; ok { delete(hub.subscribers, ch) close(ch) } hub.mutex.Unlock() } func Query(level, keyword string, page, pageSize int) ([]LogEntry, int) { if page < 1 { page = 1 } if pageSize < 1 { pageSize = 20 } if pageSize > 200 { pageSize = 200 } level = strings.TrimSpace(strings.ToLower(level)) keyword = strings.TrimSpace(strings.ToLower(keyword)) hub.mutex.RLock() defer hub.mutex.RUnlock() filtered := make([]LogEntry, 0, len(hub.entries)) for _, e := range hub.entries { if level != "" && e.Level != level { continue } if keyword != "" && !strings.Contains(strings.ToLower(e.Message), keyword) { continue } filtered = append(filtered, e) } total := len(filtered) start := (page - 1) * pageSize if start >= total { return []LogEntry{}, total } end := start + pageSize if end > total { end = total } return filtered[start:end], total }