залил
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user