package logging import ( "fmt" "os" "runtime" "strings" "sync" "syscall" "time" "golang.org/x/text/encoding/charmap" ) // FileLogger реализация Logger для записи в файл type FileLogger struct { logFile *os.File logFileMu sync.Mutex logFileSize int64 logLevel string fileName string maxSizeMB int logToFile bool } // NewFileLogger создает новый файловый логгер func NewFileLogger(fileName string, logLevel string, maxSizeMB int, logToFile bool) (*FileLogger, error) { logger := &FileLogger{ logLevel: logLevel, fileName: fileName, maxSizeMB: maxSizeMB, logToFile: logToFile, } if logToFile { if err := logger.initLogFile(); err != nil { return nil, fmt.Errorf("ошибка инициализации лог файла: %w", err) } } return logger, nil } // Info логирует информационное сообщение func (l *FileLogger) Info(format string, args ...interface{}) { if l.logLevel == "debug" || l.logLevel == "info" { message := fmt.Sprintf(format, args...) l.writeToLogFile("INFO", message) } } // Debug логирует отладочное сообщение func (l *FileLogger) Debug(format string, args ...interface{}) { if l.logLevel == "debug" { message := fmt.Sprintf(format, args...) l.writeToLogFile("DEBUG", message) } } // Warning логирует предупреждение func (l *FileLogger) Warning(format string, args ...interface{}) { message := fmt.Sprintf(format, args...) l.writeToLogFile("WARNING", message) } // Error логирует ошибку func (l *FileLogger) Error(format string, args ...interface{}) { message := fmt.Sprintf(format, args...) l.writeToLogFile("ERROR", message) } // Success логирует успешное выполнение func (l *FileLogger) Success(format string, args ...interface{}) { message := fmt.Sprintf(format, args...) l.writeToLogFile("SUCCESS", message) } // Close закрывает лог файл func (l *FileLogger) Close() { if l.logFile != nil { l.logFile.Close() } } // initLogFile инициализирует лог файл func (l *FileLogger) initLogFile() error { if l.fileName == "" { l.fileName = "audio-catalyst.log" } // Устанавливаем UTF-8 кодировку для Windows консоли if runtime.GOOS == "windows" { kernel32 := syscall.NewLazyDLL("kernel32.dll") setConsoleOutputCP := kernel32.NewProc("SetConsoleOutputCP") setConsoleOutputCP.Call(uintptr(65001)) // UTF-8 } var err error l.logFile, err = os.OpenFile(l.fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { return fmt.Errorf("не удалось открыть лог файл: %w", err) } // Получаем текущий размер файла if stat, err := l.logFile.Stat(); err == nil { l.logFileSize = stat.Size() } return nil } // writeToLogFile записывает сообщение в лог файл func (l *FileLogger) writeToLogFile(level, message string) { if !l.logToFile || l.logFile == nil { return } l.logFileMu.Lock() defer l.logFileMu.Unlock() timestamp := time.Now().Format("02.01.2006 15:04:05") raw := fmt.Sprintf("[%s] [%s] %s\n", timestamp, level, message) var toWrite string if runtime.GOOS == "windows" { s := l.sanitizeForWindowsCP1251(raw) enc := charmap.Windows1251.NewEncoder() if encStr, err := enc.String(s); err == nil { toWrite = encStr } else { toWrite, _ = enc.String(l.sanitizeForWindowsCP1251(s)) } } else { toWrite = raw } // Проверяем размер файла и выполняем ротацию maxSize := int64(l.maxSizeMB) * 1024 * 1024 if l.logFileSize+int64(len(toWrite)) > maxSize { l.logFile.Close() var err error l.logFile, err = os.OpenFile(l.fileName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) if err != nil { return } l.logFileSize = 0 } if n, err := l.logFile.WriteString(toWrite); err == nil { l.logFileSize += int64(n) l.logFile.Sync() } } // sanitizeForWindowsCP1251 санитизирует строку для Windows-1251 func (l *FileLogger) sanitizeForWindowsCP1251(s string) string { var b strings.Builder for _, r := range s { switch r { case '\n', '\r', '\t': b.WriteRune(r) continue } // Кириллица if (r >= 0x0410 && r <= 0x044F) || r == 0x0401 || r == 0x0451 { b.WriteRune(r) continue } // ASCII печатаемые символы if r >= 0x20 && r <= 0x7E { b.WriteRune(r) continue } // Распространённые знаки пунктуации if (r >= 0x2013 && r <= 0x201E) || r == 0x00AB || r == 0x00BB { b.WriteRune(r) continue } } return b.String() }