TTW_Bot_GO/internal/logger/logger.go

149 lines
3.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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...)
}