- Реализовать тесты для поиска MP3-файлов и переименования/организации папок книг в репозитории файловой системы. - Создать FileLogger для записи сообщений в файл с поддержкой различных уровней журналирования и управления размером файлов. - Разработать репозиторий RuTracker для обработки поиска торрентов, получения метаданных и загрузки торрент-файлов. - Добавить тесты для нормализации URL в репозиторий RuTracker. - Реализовать адаптер логгера TUI для отображения логов в терминальном интерфейсе и, при необходимости, для записи логов в базовый логгер. - Создать менеджер TUI для управления пользовательским интерфейсом приложения, включая главное меню, экран обработки, настройки и отображение результатов.
186 lines
4.6 KiB
Go
186 lines
4.6 KiB
Go
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()
|
|
}
|