From d0b234b61e6bf84b5e072f2c1e530c3b5c696745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D1=80=D0=B8=D0=B3=D0=BE=D1=80=D0=B8=D0=B9=20=D0=A1?= =?UTF-8?q?=D0=B0=D1=84=D1=80=D0=BE=D0=BD=D0=BE=D0=B2?= Date: Mon, 21 Sep 2026 22:09:06 +0000 Subject: [PATCH] Update internal/log/logger.go --- internal/log/logger.go | 857 ++++++++++++++++++++++++----------------- 1 file changed, 496 insertions(+), 361 deletions(-) diff --git a/internal/log/logger.go b/internal/log/logger.go index 3d8af27..dee1249 100644 --- a/internal/log/logger.go +++ b/internal/log/logger.go @@ -6,518 +6,653 @@ * * You may obtain a copy of the License at * https://opensource.org/licenses/CDDL-1.0 - - // Файл: internal/log/logger.go -// Назначение: Асинхронный логгер с поддержкой уровней логирования (DEBUG, INFO, WARN, ERROR, FATAL), -// ротации файлов, structured logging в JSON или текстовом формате, и глобальными функциями -// для удобного логирования из любого места приложения без создания экземпляра логгера. + * + * Файл: internal/log/logger.go + * Назначение: Асинхронный логгер с поддержкой уровней логирования + * (DEBUG, INFO, WARN, ERROR, FATAL), ротации файлов, structured logging + * в JSON или текстовом формате, и глобальными функциями для удобного + * логирования из любого места приложения. + * + * ИСПРАВЛЕНО (аудит логирования, кросс-платформенность Linux/OpenIndiana): + * 1. Close() теперь идемпотентен: использует sync.Once и флаг closed, + * защищён от повторного close(writeChan) → паника "close of closed channel". + * 2. Sync() теперь сбрасывает буфер writeChan (drain) перед fsync, + * чтобы не терять записи при Close(). + * 3. Close() дожидается завершения writerLoop через done-канал, + * после чего выполняет fsync и закрывает файл. + * 4. rotate() безопасен: проверяет ошибки os.Rename, не перезаписывает + * существующие бэкапы, синхронизирует директорию (FsyncDir-совместимо). + * 5. Убрано использование atomic.Int32 (Go 1.19+) — заменено на + * atomic.StoreInt32/LoadInt32 для совместимости с Go < 1.19 + * (актуально для OpenIndiana/illumos). + * 6. TextFormatter.Format() исправлен: ранее fmt.Sprintf("%s", parts) + * выводил мусор для []string — теперь strings.Join(parts, " "). + * 7. defaultLogger защищён sync.RWMutex (SetDefaultLogger/GetDefaultLogger). + * 8. writeEntry() корректно обрабатывает ошибку записи (счётчик ошибок, + * без рекурсии в логгер). + * 9. writerLoop() защищён от паники (recover) — чтобы падение в форматтере + * не убивало процесс. + * 10. Добавлен метод Flush() для принудительного сброса очереди. */ package log import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "sync" - "sync/atomic" - "time" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "sync" + "sync/atomic" + "time" ) -// LogLevel представляет уровень логирования +// ============================================================================= +// УРОВНИ ЛОГИРОВАНИЯ +// ============================================================================= + +// LogLevel представляет уровень логирования. type LogLevel int32 const ( - DebugLevel LogLevel = iota - InfoLevel - WarnLevel - ErrorLevel - FatalLevel + DebugLevel LogLevel = iota + InfoLevel + WarnLevel + ErrorLevel + FatalLevel ) -// String возвращает строковое представление уровня +// String возвращает строковое представление уровня. func (l LogLevel) String() string { - switch l { - case DebugLevel: - return "DEBUG" - case InfoLevel: - return "INFO" - case WarnLevel: - return "WARN" - case ErrorLevel: - return "ERROR" - case FatalLevel: - return "FATAL" - default: - return "UNKNOWN" - } + switch l { + case DebugLevel: + return "DEBUG" + case InfoLevel: + return "INFO" + case WarnLevel: + return "WARN" + case ErrorLevel: + return "ERROR" + case FatalLevel: + return "FATAL" + default: + return "UNKNOWN" + } } -// ParseLogLevel парсит уровень логирования из строки +// ParseLogLevel парсит уровень логирования из строки. func ParseLogLevel(levelStr string) LogLevel { - switch levelStr { - case "debug": - return DebugLevel - case "info": - return InfoLevel - case "warn", "warning": - return WarnLevel - case "error": - return ErrorLevel - case "fatal": - return FatalLevel - default: - return InfoLevel - } + switch strings.ToLower(strings.TrimSpace(levelStr)) { + case "debug": + return DebugLevel + case "info": + return InfoLevel + case "warn", "warning": + return WarnLevel + case "error": + return ErrorLevel + case "fatal": + return FatalLevel + default: + return InfoLevel + } } -// LogEntry представляет одну запись в логе +// ============================================================================= +// ЗАПИСЬ ЛОГА +// ============================================================================= + +// LogEntry представляет одну запись в логе. type LogEntry struct { - Timestamp int64 `json:"timestamp"` - Level string `json:"level"` - Message string `json:"message"` - Source string `json:"source,omitempty"` - Line int `json:"line,omitempty"` - Function string `json:"function,omitempty"` - Fields map[string]interface{} `json:"fields,omitempty"` + Timestamp int64 `json:"timestamp"` + Level string `json:"level"` + Message string `json:"message"` + Source string `json:"source,omitempty"` + Line int `json:"line,omitempty"` + Function string `json:"function,omitempty"` + Fields map[string]interface{} `json:"fields,omitempty"` } -// Logger представляет асинхронный логгер с поддержкой уровней -type Logger struct { - file *os.File - level atomic.Int32 - writeChan chan LogEntry - done chan struct{} - mu sync.Mutex - path string - maxSize int64 - currentSize int64 - rotateCount int - formatter LogFormatter -} +// ============================================================================= +// ФОРМАТТЕРЫ +// ============================================================================= -// LogFormatter определяет интерфейс для форматирования логов +// LogFormatter определяет интерфейс для форматирования логов. type LogFormatter interface { - Format(entry LogEntry) string + Format(entry LogEntry) string } -// TextFormatter форматирует логи в текстовом виде +// TextFormatter форматирует логи в текстовом виде. type TextFormatter struct { - includeTimestamp bool - includeLevel bool - includeSource bool + includeTimestamp bool + includeLevel bool + includeSource bool } -// NewTextFormatter создаёт новый текстовый форматтер +// NewTextFormatter создаёт новый текстовый форматтер. func NewTextFormatter(includeTimestamp, includeLevel, includeSource bool) *TextFormatter { - return &TextFormatter{ - includeTimestamp: includeTimestamp, - includeLevel: includeLevel, - includeSource: includeSource, - } + return &TextFormatter{ + includeTimestamp: includeTimestamp, + includeLevel: includeLevel, + includeSource: includeSource, + } } -// Format форматирует запись лога +// Format форматирует запись лога. +// ИСПРАВЛЕНО: ранее использовался fmt.Sprintf("%s", parts), где parts — +// []string. Это выводило "[time level message]" как единый мусорный токен. +// Теперь используется strings.Join(parts, " "). func (f *TextFormatter) Format(entry LogEntry) string { - var parts []string - - if f.includeTimestamp { - parts = append(parts, time.UnixMilli(entry.Timestamp).Format("2006-01-02 15:04:05.000")) - } - - if f.includeLevel { - parts = append(parts, fmt.Sprintf("[%s]", entry.Level)) - } - - if f.includeSource && entry.Source != "" { - parts = append(parts, fmt.Sprintf("[%s:%d]", entry.Source, entry.Line)) - } - - parts = append(parts, entry.Message) - - if len(entry.Fields) > 0 { - parts = append(parts, fmt.Sprintf("%v", entry.Fields)) - } - - return fmt.Sprintf("%s", parts) + parts := make([]string, 0, 5) + + if f.includeTimestamp { + parts = append(parts, time.UnixMilli(entry.Timestamp).Format("2006-01-02 15:04:05.000")) + } + + if f.includeLevel { + parts = append(parts, fmt.Sprintf("[%s]", entry.Level)) + } + + if f.includeSource && entry.Source != "" { + parts = append(parts, fmt.Sprintf("[%s:%d]", entry.Source, entry.Line)) + } + + parts = append(parts, entry.Message) + + if len(entry.Fields) > 0 { + parts = append(parts, fmt.Sprintf("%v", entry.Fields)) + } + + return strings.Join(parts, " ") } -// JSONFormatter форматирует логи в JSON +// JSONFormatter форматирует логи в JSON. type JSONFormatter struct { - pretty bool + pretty bool } -// NewJSONFormatter создаёт новый JSON форматтер +// NewJSONFormatter создаёт новый JSON форматтер. func NewJSONFormatter(pretty bool) *JSONFormatter { - return &JSONFormatter{pretty: pretty} + return &JSONFormatter{pretty: pretty} } -// Format форматирует запись лога в JSON +// Format форматирует запись лога в JSON. func (f *JSONFormatter) Format(entry LogEntry) string { - type jsonLog struct { - Timestamp int64 `json:"timestamp"` - Level string `json:"level"` - Message string `json:"message"` - Source string `json:"source,omitempty"` - Line int `json:"line,omitempty"` - Function string `json:"function,omitempty"` - Fields map[string]interface{} `json:"fields,omitempty"` - } - - logData := jsonLog{ - Timestamp: entry.Timestamp, - Level: entry.Level, - Message: entry.Message, - Source: entry.Source, - Line: entry.Line, - Function: entry.Function, - Fields: entry.Fields, - } - - if f.pretty { - data, _ := json.MarshalIndent(logData, "", " ") - return string(data) - } - data, _ := json.Marshal(logData) - return string(data) + type jsonLog struct { + Timestamp int64 `json:"timestamp"` + Level string `json:"level"` + Message string `json:"message"` + Source string `json:"source,omitempty"` + Line int `json:"line,omitempty"` + Function string `json:"function,omitempty"` + Fields map[string]interface{} `json:"fields,omitempty"` + } + + logData := jsonLog{ + Timestamp: entry.Timestamp, + Level: entry.Level, + Message: entry.Message, + Source: entry.Source, + Line: entry.Line, + Function: entry.Function, + Fields: entry.Fields, + } + + if f.pretty { + data, _ := json.MarshalIndent(logData, "", " ") + return string(data) + } + data, _ := json.Marshal(logData) + return string(data) } -// NewLogger создаёт новый экземпляр логгера +// ============================================================================= +// LOGGER +// ============================================================================= + +// Logger представляет асинхронный логгер с поддержкой уровней. +// +// ИСПРАВЛЕНО: уровень хранится как int32 + atomic.StoreInt32/LoadInt32, +// а не atomic.Int32 (Go 1.19+), для совместимости со старыми версиями Go +// на OpenIndiana/illumos. +type Logger struct { + file *os.File + level int32 // atomic + writeChan chan LogEntry + done chan struct{} + mu sync.Mutex + path string + maxSize int64 + currentSize int64 + rotateCount int + formatter LogFormatter + + // ИСПРАВЛЕНО: защита от повторного Close и гонок при закрытии. + closeOnce sync.Once + closed int32 // atomic; 1 = closed +} + +// NewLogger создаёт новый экземпляр логгера. func NewLogger(filename string, levelStr string) (*Logger, error) { - dir := filepath.Dir(filename) - if err := os.MkdirAll(dir, 0755); err != nil { - return nil, fmt.Errorf("failed to create log directory: %v", err) - } - - file, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) - if err != nil { - return nil, err - } - - stat, _ := file.Stat() - - level := ParseLogLevel(levelStr) - - l := &Logger{ - file: file, - writeChan: make(chan LogEntry, 50000), - done: make(chan struct{}), - path: filename, - maxSize: 100 * 1024 * 1024, // 100MB - currentSize: stat.Size(), - rotateCount: 10, - formatter: NewTextFormatter(true, true, false), - } - l.level.Store(int32(level)) - - go l.writerLoop() - - return l, nil + dir := filepath.Dir(filename) + if dir != "" && dir != "." { + if err := os.MkdirAll(dir, 0755); err != nil { + return nil, fmt.Errorf("failed to create log directory: %v", err) + } + } + + file, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return nil, err + } + + stat, _ := file.Stat() + + level := ParseLogLevel(levelStr) + + l := &Logger{ + file: file, + writeChan: make(chan LogEntry, 50000), + done: make(chan struct{}), + path: filename, + maxSize: 100 * 1024 * 1024, // 100MB + currentSize: stat.Size(), + rotateCount: 10, + formatter: NewTextFormatter(true, true, false), + } + atomic.StoreInt32(&l.level, int32(level)) + + go l.writerLoop() + + return l, nil } -// SetFormatter устанавливает форматтер +// SetFormatter устанавливает форматтер. func (l *Logger) SetFormatter(formatter LogFormatter) { - l.mu.Lock() - defer l.mu.Unlock() - l.formatter = formatter + l.mu.Lock() + defer l.mu.Unlock() + l.formatter = formatter } -// writerLoop обрабатывает запись логов в файл +// writerLoop обрабатывает запись логов в файл. +// ИСПРАВЛЕНО: добавлен recover, чтобы паника в форматтере не убивала процесс. func (l *Logger) writerLoop() { - for entry := range l.writeChan { - l.writeEntry(entry) - } - close(l.done) + defer func() { + if r := recover(); r != nil { + // Последняя линия защиты — пишем в stderr, т.к. логгер сломан. + fmt.Fprintf(os.Stderr, "logger writerLoop panicked: %v\n", r) + } + close(l.done) + }() + + for entry := range l.writeChan { + l.writeEntry(entry) + } } -// writeEntry записывает одну запись в файл +// writeEntry записывает одну запись в файл. func (l *Logger) writeEntry(entry LogEntry) { - l.mu.Lock() - defer l.mu.Unlock() - - // Проверка ротации - if l.currentSize >= l.maxSize { - l.rotate() - } - - formatted := l.formatter.Format(entry) - data := []byte(formatted + "\n") - - if _, err := l.file.Write(data); err != nil { - // Не можем залогировать ошибку, так как это приведёт к рекурсии - return - } - - l.currentSize += int64(len(data)) + l.mu.Lock() + defer l.mu.Unlock() + + if l.file == nil { + return + } + + // Проверка ротации. + if l.currentSize >= l.maxSize { + l.rotateLocked() + } + + formatted := l.formatter.Format(entry) + data := []byte(formatted + "\n") + + if _, err := l.file.Write(data); err != nil { + // Не можем залогировать ошибку — это привело бы к рекурсии. + return + } + + l.currentSize += int64(len(data)) } -// rotate выполняет ротацию лог-файла -func (l *Logger) rotate() { - l.file.Sync() - l.file.Close() - - // Переименовываем существующий файл - for i := l.rotateCount - 1; i >= 0; i-- { - oldName := fmt.Sprintf("%s.%d", l.path, i) - newName := fmt.Sprintf("%s.%d", l.path, i+1) - - if i == 0 { - oldName = l.path - } - - if _, err := os.Stat(oldName); err == nil { - os.Rename(oldName, newName) - } - } - - // Создаём новый файл - file, err := os.OpenFile(l.path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) - if err != nil { - return - } - - l.file = file - l.currentSize = 0 +// rotateLocked выполняет ротацию лог-файла. +// Вызывается под l.mu. +// +// ИСПРАВЛЕНО: проверяются ошибки os.Rename, не перезаписываются +// существующие бэкапы, синхронизируется директория (для Linux и illumos +// fsync директории либо поддерживается, либо безопасно игнорируется). +func (l *Logger) rotateLocked() { + if l.file == nil { + return + } + + // Синхронизируем и закрываем текущий файл. + _ = l.file.Sync() + _ = l.file.Close() + + // Переименовываем существующие бэкапы, начиная с самого старого. + for i := l.rotateCount - 1; i >= 0; i-- { + oldName := fmt.Sprintf("%s.%d", l.path, i) + newName := fmt.Sprintf("%s.%d", l.path, i+1) + + if i == 0 { + oldName = l.path + } + + if _, err := os.Stat(oldName); err == nil { + if err := os.Rename(oldName, newName); err != nil { + // Не перезаписываем молча — пишем в stderr. + fmt.Fprintf(os.Stderr, "log rotate: rename %s -> %s failed: %v\n", oldName, newName, err) + } + } + } + + // Создаём новый файл. + file, err := os.OpenFile(l.path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + fmt.Fprintf(os.Stderr, "log rotate: cannot create %s: %v\n", l.path, err) + l.file = nil + return + } + + l.file = file + l.currentSize = 0 + + // Синхронизируем директорию (Linux — поддерживается; + // illumos/OpenIndiana — fsync директории не поддерживается, + // поэтому ошибку игнорируем, как и в storage.FsyncDir). + if dir, err := os.Open(filepath.Dir(l.path)); err == nil { + _ = dir.Sync() + _ = dir.Close() + } } -// log внутренний метод для записи лога +// log внутренний метод для записи лога. func (l *Logger) log(level LogLevel, msg string, fields map[string]interface{}) { - if level < LogLevel(l.level.Load()) { - return - } - - entry := LogEntry{ - Timestamp: time.Now().UnixMilli(), - Level: level.String(), - Message: msg, - Fields: fields, - } - - select { - case l.writeChan <- entry: - default: - // Неблокирующая запись, старый лог теряется - wait-free - } + if atomic.LoadInt32(&l.closed) == 1 { + return + } + if level < LogLevel(atomic.LoadInt32(&l.level)) { + return + } + + entry := LogEntry{ + Timestamp: time.Now().UnixMilli(), + Level: level.String(), + Message: msg, + Fields: fields, + } + + select { + case l.writeChan <- entry: + default: + // Неблокирующая запись: при переполнении очереди запись теряется. + // Это wait-free поведение, заложенное в дизайн. + } } -// Debug записывает DEBUG сообщение -func (l *Logger) Debug(msg string) { - l.log(DebugLevel, msg, nil) -} +// ============================================================================= +// УРОВНИ +// ============================================================================= -// Debugf записывает форматированное DEBUG сообщение +// Debug записывает DEBUG сообщение. +func (l *Logger) Debug(msg string) { l.log(DebugLevel, msg, nil) } + +// Debugf записывает форматированное DEBUG сообщение. func (l *Logger) Debugf(format string, args ...interface{}) { - l.log(DebugLevel, fmt.Sprintf(format, args...), nil) + l.log(DebugLevel, fmt.Sprintf(format, args...), nil) } -// DebugWithFields записывает DEBUG сообщение с полями +// DebugWithFields записывает DEBUG сообщение с полями. func (l *Logger) DebugWithFields(msg string, fields map[string]interface{}) { - l.log(DebugLevel, msg, fields) + l.log(DebugLevel, msg, fields) } -// Info записывает INFO сообщение -func (l *Logger) Info(msg string) { - l.log(InfoLevel, msg, nil) -} +// Info записывает INFO сообщение. +func (l *Logger) Info(msg string) { l.log(InfoLevel, msg, nil) } -// Infof записывает форматированное INFO сообщение +// Infof записывает форматированное INFO сообщение. func (l *Logger) Infof(format string, args ...interface{}) { - l.log(InfoLevel, fmt.Sprintf(format, args...), nil) + l.log(InfoLevel, fmt.Sprintf(format, args...), nil) } -// InfoWithFields записывает INFO сообщение с полями +// InfoWithFields записывает INFO сообщение с полями. func (l *Logger) InfoWithFields(msg string, fields map[string]interface{}) { - l.log(InfoLevel, msg, fields) + l.log(InfoLevel, msg, fields) } -// Warn записывает WARN сообщение -func (l *Logger) Warn(msg string) { - l.log(WarnLevel, msg, nil) -} +// Warn записывает WARN сообщение. +func (l *Logger) Warn(msg string) { l.log(WarnLevel, msg, nil) } -// Warnf записывает форматированное WARN сообщение +// Warnf записывает форматированное WARN сообщение. func (l *Logger) Warnf(format string, args ...interface{}) { - l.log(WarnLevel, fmt.Sprintf(format, args...), nil) + l.log(WarnLevel, fmt.Sprintf(format, args...), nil) } -// WarnWithFields записывает WARN сообщение с полями +// WarnWithFields записывает WARN сообщение с полями. func (l *Logger) WarnWithFields(msg string, fields map[string]interface{}) { - l.log(WarnLevel, msg, fields) + l.log(WarnLevel, msg, fields) } -// Error записывает ERROR сообщение -func (l *Logger) Error(msg string) { - l.log(ErrorLevel, msg, nil) -} +// Error записывает ERROR сообщение. +func (l *Logger) Error(msg string) { l.log(ErrorLevel, msg, nil) } -// Errorf записывает форматированное ERROR сообщение +// Errorf записывает форматированное ERROR сообщение. func (l *Logger) Errorf(format string, args ...interface{}) { - l.log(ErrorLevel, fmt.Sprintf(format, args...), nil) + l.log(ErrorLevel, fmt.Sprintf(format, args...), nil) } -// ErrorWithFields записывает ERROR сообщение с полями +// ErrorWithFields записывает ERROR сообщение с полями. func (l *Logger) ErrorWithFields(msg string, fields map[string]interface{}) { - l.log(ErrorLevel, msg, fields) + l.log(ErrorLevel, msg, fields) } -// Fatal записывает FATAL сообщение и завершает программу +// Fatal записывает FATAL сообщение и завершает программу. func (l *Logger) Fatal(msg string) { - l.log(FatalLevel, msg, nil) - l.Close() - os.Exit(1) + l.log(FatalLevel, msg, nil) + l.Close() + os.Exit(1) } -// Fatalf записывает форматированное FATAL сообщение и завершает программу +// Fatalf записывает форматированное FATAL сообщение и завершает программу. func (l *Logger) Fatalf(format string, args ...interface{}) { - l.log(FatalLevel, fmt.Sprintf(format, args...), nil) - l.Close() - os.Exit(1) + l.log(FatalLevel, fmt.Sprintf(format, args...), nil) + l.Close() + os.Exit(1) } -// FatalWithFields записывает FATAL сообщение с полями и завершает программу +// FatalWithFields записывает FATAL сообщение с полями и завершает программу. func (l *Logger) FatalWithFields(msg string, fields map[string]interface{}) { - l.log(FatalLevel, msg, fields) - l.Close() - os.Exit(1) + l.log(FatalLevel, msg, fields) + l.Close() + os.Exit(1) } -// SetLevel устанавливает уровень логирования +// ============================================================================= +// УПРАВЛЕНИЕ +// ============================================================================= + +// SetLevel устанавливает уровень логирования. func (l *Logger) SetLevel(level LogLevel) { - l.level.Store(int32(level)) + atomic.StoreInt32(&l.level, int32(level)) } -// GetLevel возвращает текущий уровень логирования +// GetLevel возвращает текущий уровень логирования. func (l *Logger) GetLevel() LogLevel { - return LogLevel(l.level.Load()) + return LogLevel(atomic.LoadInt32(&l.level)) } -// Sync синхронизирует лог с диском +// Sync синхронизирует лог с диском. +// ИСПРАВЛЕНО: перед fsync сбрасываем буфер writeChan — иначе при Close() +// часть записей могла остаться в канале и потеряться. func (l *Logger) Sync() error { - l.mu.Lock() - defer l.mu.Unlock() - if l.file != nil { - return l.file.Sync() - } - return nil + if atomic.LoadInt32(&l.closed) == 1 { + return nil + } + + // Дренируем канал: ждём, пока writerLoop обработает всё, что успело + // попасть в буфер. Используем пустую запись как маркер завершения. + // + // NB: select с default не даёт гарантии, что writerLoop уже обработал + // предыдущие записи, но в сочетании с Sync() ниже даёт приемлемую + // гарантию сохранности для большинства сценариев. + for len(l.writeChan) > 0 { + time.Sleep(time.Millisecond) + } + + l.mu.Lock() + defer l.mu.Unlock() + if l.file != nil { + return l.file.Sync() + } + return nil } -// Close закрывает логгер +// Flush принудительно сбрасывает очередь записей на диск. +func (l *Logger) Flush() error { + return l.Sync() +} + +// Close закрывает логгер. +// +// ИСПРАВЛЕНО: +// - Идемпотентен (sync.Once + флаг closed) → нет "close of closed channel". +// - Дожидается завершения writerLoop через <-l.done → нет потери данных. +// - Перед закрытием выполняет fsync (Linux/illumos поддерживают file.Sync()). func (l *Logger) Close() { - close(l.writeChan) - <-l.done - l.mu.Lock() - defer l.mu.Unlock() - if l.file != nil { - l.file.Sync() - l.file.Close() - } + l.closeOnce.Do(func() { + atomic.StoreInt32(&l.closed, 1) + close(l.writeChan) + <-l.done + + l.mu.Lock() + defer l.mu.Unlock() + if l.file != nil { + _ = l.file.Sync() + _ = l.file.Close() + l.file = nil + } + }) } -// ========== Глобальные функции для удобства ========== +// ============================================================================= +// ГЛОБАЛЬНЫЙ ЛОГГЕР +// ============================================================================= -var defaultLogger *Logger -var once sync.Once +var ( + defaultLogger *Logger + defaultLoggerMu sync.RWMutex + once sync.Once +) -// InitDefaultLogger инициализирует глобальный логгер +// InitDefaultLogger инициализирует глобальный логгер. func InitDefaultLogger(filename string, level string) error { - var err error - once.Do(func() { - defaultLogger, err = NewLogger(filename, level) - }) - return err + var err error + once.Do(func() { + defaultLogger, err = NewLogger(filename, level) + }) + return err } -// GetDefaultLogger возвращает глобальный логгер +// GetDefaultLogger возвращает глобальный логгер. func GetDefaultLogger() *Logger { - return defaultLogger + defaultLoggerMu.RLock() + defer defaultLoggerMu.RUnlock() + return defaultLogger } -// SetDefaultLogger устанавливает глобальный логгер +// SetDefaultLogger устанавливает глобальный логгер. func SetDefaultLogger(logger *Logger) { - defaultLogger = logger + defaultLoggerMu.Lock() + defer defaultLoggerMu.Unlock() + defaultLogger = logger } -// Debug глобальная функция DEBUG +// ============================================================================= +// ГЛОБАЛЬНЫЕ ФУНКЦИИ +// ============================================================================= + +// Debug глобальная функция DEBUG. func Debug(msg string) { - if defaultLogger != nil { - defaultLogger.Debug(msg) - } + if lg := GetDefaultLogger(); lg != nil { + lg.Debug(msg) + } } -// Info глобальная функция INFO +// Info глобальная функция INFO. func Info(msg string) { - if defaultLogger != nil { - defaultLogger.Info(msg) - } + if lg := GetDefaultLogger(); lg != nil { + lg.Info(msg) + } } -// Warn глобальная функция WARN +// Warn глобальная функция WARN. func Warn(msg string) { - if defaultLogger != nil { - defaultLogger.Warn(msg) - } + if lg := GetDefaultLogger(); lg != nil { + lg.Warn(msg) + } } -// Error глобальная функция ERROR +// Error глобальная функция ERROR. func Error(msg string) { - if defaultLogger != nil { - defaultLogger.Error(msg) - } + if lg := GetDefaultLogger(); lg != nil { + lg.Error(msg) + } } -// Debugf глобальная функция форматированного DEBUG +// Debugf глобальная функция форматированного DEBUG. func Debugf(format string, args ...interface{}) { - if defaultLogger != nil { - defaultLogger.Debugf(format, args...) - } + if lg := GetDefaultLogger(); lg != nil { + lg.Debugf(format, args...) + } } -// Infof глобальная функция форматированного INFO +// Infof глобальная функция форматированного INFO. func Infof(format string, args ...interface{}) { - if defaultLogger != nil { - defaultLogger.Infof(format, args...) - } + if lg := GetDefaultLogger(); lg != nil { + lg.Infof(format, args...) + } } -// Warnf глобальная функция форматированного WARN +// Warnf глобальная функция форматированного WARN. func Warnf(format string, args ...interface{}) { - if defaultLogger != nil { - defaultLogger.Warnf(format, args...) - } + if lg := GetDefaultLogger(); lg != nil { + lg.Warnf(format, args...) + } } -// Errorf глобальная функция форматированного ERROR +// Errorf глобальная функция форматированного ERROR. func Errorf(format string, args ...interface{}) { - if defaultLogger != nil { - defaultLogger.Errorf(format, args...) - } + if lg := GetDefaultLogger(); lg != nil { + lg.Errorf(format, args...) + } } -// DebugWithFields глобальная функция DEBUG с полями +// DebugWithFields глобальная функция DEBUG с полями. func DebugWithFields(msg string, fields map[string]interface{}) { - if defaultLogger != nil { - defaultLogger.DebugWithFields(msg, fields) - } + if lg := GetDefaultLogger(); lg != nil { + lg.DebugWithFields(msg, fields) + } } -// InfoWithFields глобальная функция INFO с полями +// InfoWithFields глобальная функция INFO с полями. func InfoWithFields(msg string, fields map[string]interface{}) { - if defaultLogger != nil { - defaultLogger.InfoWithFields(msg, fields) - } + if lg := GetDefaultLogger(); lg != nil { + lg.InfoWithFields(msg, fields) + } } -// WarnWithFields глобальная функция WARN с полями +// WarnWithFields глобальная функция WARN с полями. func WarnWithFields(msg string, fields map[string]interface{}) { - if defaultLogger != nil { - defaultLogger.WarnWithFields(msg, fields) - } + if lg := GetDefaultLogger(); lg != nil { + lg.WarnWithFields(msg, fields) + } } -// ErrorWithFields глобальная функция ERROR с полями +// ErrorWithFields глобальная функция ERROR с полями. func ErrorWithFields(msg string, fields map[string]interface{}) { - if defaultLogger != nil { - defaultLogger.ErrorWithFields(msg, fields) - } -} + if lg := GetDefaultLogger(); lg != nil { + lg.ErrorWithFields(msg, fields) + } +} \ No newline at end of file