Добавить тесты репозитория файловой системы и реализовать функциональность журналирования файлов.
- Реализовать тесты для поиска MP3-файлов и переименования/организации папок книг в репозитории файловой системы. - Создать FileLogger для записи сообщений в файл с поддержкой различных уровней журналирования и управления размером файлов. - Разработать репозиторий RuTracker для обработки поиска торрентов, получения метаданных и загрузки торрент-файлов. - Добавить тесты для нормализации URL в репозиторий RuTracker. - Реализовать адаптер логгера TUI для отображения логов в терминальном интерфейсе и, при необходимости, для записи логов в базовый логгер. - Создать менеджер TUI для управления пользовательским интерфейсом приложения, включая главное меню, экран обработки, настройки и отображение результатов.
This commit is contained in:
185
internal/infrastructure/logging/file_logger.go
Normal file
185
internal/infrastructure/logging/file_logger.go
Normal file
@@ -0,0 +1,185 @@
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user