166 lines
4.2 KiB
Go
166 lines
4.2 KiB
Go
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
|
||
}
|