package logger import ( "fmt" "log" "os" "sync" "time" ) type LogLevel string const ( LevelInfo LogLevel = "INFO" LevelWarn LogLevel = "WARN" LevelError LogLevel = "ERROR" LevelFatal LogLevel = "FATAL" LevelDebug LogLevel = "DEBUG" ) type LogEntry struct { Time time.Time `json:"time"` Level LogLevel `json:"level"` Message string `json:"message"` } var ( mu sync.Mutex // Кольцевой буфер последних записей (максимум 1000) buffer []LogEntry bufferIdx int bufferCap = 1000 // Канал для подписчиков (один на всех, но можно расширить) subscribers []chan LogEntry subMu sync.RWMutex ) func Init(filename string) error { buffer = make([]LogEntry, bufferCap) bufferIdx = 0 return nil } // Добавляет запись в буфер и рассылает подписчикам func addEntry(level LogLevel, format string, v ...interface{}) { msg := fmt.Sprintf(format, v...) entry := LogEntry{ Time: time.Now(), Level: level, Message: msg, } // Сохраняем в буфер mu.Lock() buffer[bufferIdx] = entry bufferIdx = (bufferIdx + 1) % bufferCap mu.Unlock() // Отправляем подписчикам (асинхронно, чтобы не блокировать) subMu.RLock() for _, ch := range subscribers { select { case ch <- entry: default: // Если канал заполнен, пропускаем (чтобы не тормозить) } } subMu.RUnlock() // Вывод в консоль log.Printf("[%s] %s", level, msg) if level == LevelFatal { os.Exit(1) } } func Info(format string, v ...interface{}) { addEntry(LevelInfo, format, v...) } func Warn(format string, v ...interface{}) { addEntry(LevelWarn, format, v...) } func Error(format string, v ...interface{}) { addEntry(LevelError, format, v...) } func Fatal(format string, v ...interface{}) { addEntry(LevelFatal, format, v...) } // GetRecent возвращает последние N записей (в хронологическом порядке) func GetRecent(limit int) []LogEntry { if limit > bufferCap { limit = bufferCap } mu.Lock() defer mu.Unlock() result := make([]LogEntry, 0, limit) // Буфер заполнен циклически, начинаем с bufferIdx-1 и идём назад start := bufferIdx - 1 if start < 0 { start = bufferCap - 1 } for i := 0; i < limit; i++ { idx := (start - i + bufferCap) % bufferCap if buffer[idx].Time.IsZero() { break } result = append([]LogEntry{buffer[idx]}, result...) } return result } // Subscribe возвращает канал для получения новых записей (буферизированный) func Subscribe() <-chan LogEntry { ch := make(chan LogEntry, 100) subMu.Lock() subscribers = append(subscribers, ch) subMu.Unlock() return ch } // Unsubscribe удаляет канал из списка подписчиков func Unsubscribe(ch <-chan LogEntry) { subMu.Lock() defer subMu.Unlock() for i, c := range subscribers { if c == ch { subscribers = append(subscribers[:i], subscribers[i+1:]...) close(c) break } } } func Debug(format string, v ...interface{}) { addEntry(LevelDebug, format, v...) } // GetAll возвращает все имеющиеся записи в порядке от старых к новым. func GetAll() []LogEntry { mu.Lock() defer mu.Unlock() result := make([]LogEntry, 0, bufferCap) // Начинаем с самого старого: индекс (bufferIdx) - это место, куда будет записана следующая запись. // Самый старый элемент находится в bufferIdx, если буфер заполнен, иначе в 0. start := 0 if buffer[bufferCap-1].Time.IsZero() { // Буфер не полностью заполнен, начинаем с 0 start = 0 } else { // Буфер заполнен, начинаем с bufferIdx (следующая позиция записи — это самое старое) start = bufferIdx } for i := 0; i < bufferCap; i++ { idx := (start + i) % bufferCap if buffer[idx].Time.IsZero() { continue } result = append(result, buffer[idx]) } return result }