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 ( fileLog *log.Logger mu sync.Mutex // Кольцевой буфер последних записей (максимум 1000) buffer []LogEntry bufferIdx int bufferCap = 1000 // Канал для подписчиков (один на всех, но можно расширить) subscribers []chan LogEntry subMu sync.RWMutex ) func Init(filename string, maxSize int64) error { f, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) if err != nil { return err } fileLog = log.New(f, "", log.LstdFlags) 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 fileLog != nil { fileLog.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...) }