// Файл: internal/log/logger.go // Назначение: Асинхронная, wait-free запись логов в файл с меткой времени // в миллисекундах. Поддержка уровней логирования и ротации. package log import ( "fmt" "os" "sync/atomic" "time" ) type LogLevel int32 const ( DebugLevel LogLevel = iota InfoLevel WarnLevel ErrorLevel ) type Logger struct { file *os.File level atomic.Int32 writeChan chan string done chan struct{} } func NewLogger(filename string, levelStr string) (*Logger, error) { file, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) if err != nil { return nil, err } level := InfoLevel switch levelStr { case "debug": level = DebugLevel case "warn": level = WarnLevel case "error": level = ErrorLevel } l := &Logger{ file: file, writeChan: make(chan string, 10000), done: make(chan struct{}), } l.level.Store(int32(level)) // Запуск wait-free writer go l.writerLoop() return l, nil } func (l *Logger) writerLoop() { for msg := range l.writeChan { l.file.WriteString(msg + "\n") } close(l.done) } func (l *Logger) log(level LogLevel, levelStr, msg string) { if level < LogLevel(l.level.Load()) { return } now := time.Now() timestamp := now.Format("2006-01-02 15:04:05") + fmt.Sprintf(".%03d", now.Nanosecond()/1e6) logMsg := fmt.Sprintf("[%s] %s: %s", timestamp, levelStr, msg) select { case l.writeChan <- logMsg: default: // Неблокирующая запись, старый лог теряется - wait-free } } func (l *Logger) Debug(msg string) { l.log(DebugLevel, "DEBUG", msg) } func (l *Logger) Info(msg string) { l.log(InfoLevel, "INFO", msg) } func (l *Logger) Warn(msg string) { l.log(WarnLevel, "WARN", msg) } func (l *Logger) Error(msg string) { l.log(ErrorLevel, "ERROR", msg) } func (l *Logger) Close() { close(l.writeChan) <-l.done l.file.Close() }