From 730facdf59105ab6ed0f158a63bc3d8ff168bb36 Mon Sep 17 00:00:00 2001 From: Maxim Date: Tue, 9 Jun 2026 16:02:47 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B0.=20=D0=98=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D1=81=D1=82=D0=B0=D0=BD?= =?UTF-8?q?=D0=B4=D0=B0=D1=80=D1=82=D0=BD=D1=8B=D1=85=20=D0=BF=D1=83=D1=82?= =?UTF-8?q?=D0=B5=D0=B9=20Linux?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/server/main.go | 23 ++++----- internal/logger/event_logger.go | 53 ++++++++++---------- internal/logger/human_logger.go | 13 ++--- internal/logger/paths.go | 85 +++++++++++++++++++++++++++++++++ internal/logger/retention.go | 62 ++++++++---------------- internal/logger/rotation.go | 33 +++++++------ scripts/view_logs.sh | 43 +++++++---------- 7 files changed, 186 insertions(+), 126 deletions(-) create mode 100644 internal/logger/paths.go diff --git a/cmd/server/main.go b/cmd/server/main.go index 34f3e28..2fc3620 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -165,15 +165,20 @@ func main() { // ===== ИНИЦИАЛИЗАЦИЯ ЛОГГЕРОВ ===== // 1. EventLogger (события системы) - eventLogger, err := logger.NewEventLogger("logs/events.log") + eventLogger, err := logger.NewEventLogger() if err != nil { log.Fatal("Ошибка инициализации EventLogger:", err) } defer eventLogger.Close() eventLogger.Event("СЕРВЕР_ЗАПУЩЕН") + // Выводим пути для информации + if logsDir, err := logger.GetLogsDir(); err == nil { + log.Printf("Логи сохраняются в: %s", logsDir) + } + // 1.5 Human-readable логгер - humanLogger, err := logger.NewHumanLogger("logs/gpio_human.log") + humanLogger, err := logger.NewHumanLogger() if err != nil { log.Fatal("Ошибка инициализации HumanLogger:", err) } @@ -184,7 +189,7 @@ func main() { monitor := logger.NewMonitor(eventLogger) // 3. DataLogger с ротацией - dataLogger, err := logger.NewRotatingLogger("logs/data", eventLogger) + dataLogger, err := logger.NewRotatingLogger(eventLogger) if err != nil { eventLogger.Event("ОШИБКА_ИНИЦИАЛИЗАЦИИ_DATA_ЛОГЕРА") log.Fatal("Ошибка инициализации DataLogger:", err) @@ -192,19 +197,11 @@ func main() { defer dataLogger.Close() eventLogger.Event("DATA_ЛОГЕР_ГОТОВ") - // 3.5 Retention (автоматическая очистка) - retention := logger.NewRetention("logs/data", 48, 5000, eventLogger) // 48 часов, 5 GB + // 3.5 Retention (автоматическая очистка) + retention := logger.NewRetention(48, 5000, eventLogger) retention.Start() eventLogger.Event("RETENTION_ГОТОВ") - // Получаем информацию о доступных данных - if oldest, newest, count := retention.GetAvailableRange(); count > 0 { - log.Printf("Доступны данные с %s по %s (%d файлов)", - oldest.Format("2006-01-02 15:04"), - newest.Format("2006-01-02 15:04"), - count) - } - // 4. RingBuffer buf := adapter.NewRingBuffer(1024 * 10) diff --git a/internal/logger/event_logger.go b/internal/logger/event_logger.go index 5e9dec5..606c0a2 100644 --- a/internal/logger/event_logger.go +++ b/internal/logger/event_logger.go @@ -4,38 +4,41 @@ import ( "encoding/json" "fmt" "os" - "path/filepath" "sync" "time" ) type EventLogger struct { - file *os.File - humanFile *os.File - mu sync.Mutex + file *os.File + humanFile *os.File + mu sync.Mutex } -func NewEventLogger(path string) (*EventLogger, error) { - // Создаём директорию для логов - dir := filepath.Dir(path) - if err := os.MkdirAll(dir, 0755); err != nil { - return nil, err - } - - // JSON файл для событий - f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) +func NewEventLogger() (*EventLogger, error) { + // Получаем пути к файлам + jsonPath, err := GetEventLogPath() if err != nil { - return nil, err + return nil, fmt.Errorf("ошибка получения пути для JSON логов: %w", err) } - - // Human-readable файл для событий - humanPath := filepath.Join(dir, "events_human.log") + + humanPath, err := GetEventHumanLogPath() + if err != nil { + return nil, fmt.Errorf("ошибка получения пути для Human логов: %w", err) + } + + // Открываем JSON файл + f, err := os.OpenFile(jsonPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return nil, fmt.Errorf("ошибка открытия JSON лога: %w", err) + } + + // Открываем Human-readable файл humanF, err := os.OpenFile(humanPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) if err != nil { f.Close() - return nil, err + return nil, fmt.Errorf("ошибка открытия Human лога: %w", err) } - + return &EventLogger{ file: f, humanFile: humanF, @@ -45,9 +48,9 @@ func NewEventLogger(path string) (*EventLogger, error) { func (l *EventLogger) Event(name string) { l.mu.Lock() defer l.mu.Unlock() - + now := time.Now() - + // JSON формат (для машин) ev := struct { Timestamp int64 `json:"ts"` @@ -58,15 +61,15 @@ func (l *EventLogger) Event(name string) { Time: now.Format("2006-01-02 15:04:05"), Event: name, } - + data, _ := json.Marshal(ev) data = append(data, '\n') l.file.Write(data) - + // Human-readable формат humanLine := fmt.Sprintf("[%s] EVENT: %s\n", now.Format("2006-01-02 15:04:05.000"), name) l.humanFile.WriteString(humanLine) - + l.file.Sync() l.humanFile.Sync() } @@ -75,4 +78,4 @@ func (l *EventLogger) Close() error { l.file.Close() l.humanFile.Close() return nil -} +} \ No newline at end of file diff --git a/internal/logger/human_logger.go b/internal/logger/human_logger.go index 4b4a97a..6b446e0 100644 --- a/internal/logger/human_logger.go +++ b/internal/logger/human_logger.go @@ -10,27 +10,28 @@ type HumanLogger struct { mu sync.Mutex } -func NewHumanLogger(path string) (*HumanLogger, error) { - if err := os.MkdirAll("logs", 0755); err != nil { +func NewHumanLogger() (*HumanLogger, error) { + path, err := GetGPIOLogPath() + if err != nil { return nil, err } - + f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) if err != nil { return nil, err } - + return &HumanLogger{file: f}, nil } func (l *HumanLogger) Write(data string) { l.mu.Lock() defer l.mu.Unlock() - + l.file.WriteString(data) l.file.Sync() } func (l *HumanLogger) Close() error { return l.file.Close() -} +} \ No newline at end of file diff --git a/internal/logger/paths.go b/internal/logger/paths.go new file mode 100644 index 0000000..cc1e906 --- /dev/null +++ b/internal/logger/paths.go @@ -0,0 +1,85 @@ +package logger + +import ( + "os" + "path/filepath" +) + +// GetDataDir возвращает стандартную директорию для данных приложения +func GetDataDir() (string, error) { + // Используем XDG Base Directory Specification + homeDir, err := os.UserHomeDir() + if err != nil { + return "", err + } + + // ~/.local/share/gpio-monitoring + dataDir := filepath.Join(homeDir, ".local", "share", "gpio-monitoring") + + // Создаём директорию если её нет + if err := os.MkdirAll(dataDir, 0755); err != nil { + return "", err + } + + return dataDir, nil +} + +// GetLogsDir возвращает директорию для логов +func GetLogsDir() (string, error) { + dataDir, err := GetDataDir() + if err != nil { + return "", err + } + + logsDir := filepath.Join(dataDir, "logs") + if err := os.MkdirAll(logsDir, 0755); err != nil { + return "", err + } + + return logsDir, nil +} + +// GetDataLogsDir возвращает директорию для бинарных данных +func GetDataLogsDir() (string, error) { + logsDir, err := GetLogsDir() + if err != nil { + return "", err + } + + dataDir := filepath.Join(logsDir, "data") + if err := os.MkdirAll(dataDir, 0755); err != nil { + return "", err + } + + return dataDir, nil +} + +// GetEventLogPath возвращает путь к файлу событий +func GetEventLogPath() (string, error) { + logsDir, err := GetLogsDir() + if err != nil { + return "", err + } + + return filepath.Join(logsDir, "events.log"), nil +} + +// GetEventHumanLogPath возвращает путь к человеко-читаемому файлу событий +func GetEventHumanLogPath() (string, error) { + logsDir, err := GetLogsDir() + if err != nil { + return "", err + } + + return filepath.Join(logsDir, "events_human.log"), nil +} + +// GetGPIOLogPath возвращает путь к человеко-читаемому GPIO логу +func GetGPIOLogPath() (string, error) { + logsDir, err := GetLogsDir() + if err != nil { + return "", err + } + + return filepath.Join(logsDir, "gpio_human.log"), nil +} \ No newline at end of file diff --git a/internal/logger/retention.go b/internal/logger/retention.go index ce90631..feda26b 100644 --- a/internal/logger/retention.go +++ b/internal/logger/retention.go @@ -10,15 +10,13 @@ import ( ) type Retention struct { - dataDir string - maxAgeHours int // максимальный возраст файлов в часах - maxSizeBytes int64 // максимальный размер всех файлов в байтах + maxAgeHours int + maxSizeBytes int64 eventLogger *EventLogger } -func NewRetention(dataDir string, maxAgeHours int, maxSizeMB int, eventLogger *EventLogger) *Retention { +func NewRetention(maxAgeHours int, maxSizeMB int, eventLogger *EventLogger) *Retention { return &Retention{ - dataDir: dataDir, maxAgeHours: maxAgeHours, maxSizeBytes: int64(maxSizeMB) * 1024 * 1024, eventLogger: eventLogger, @@ -41,29 +39,38 @@ func (r *Retention) Start() { } func (r *Retention) Cleanup() { + // Получаем директорию с данными + dataDir, err := GetDataLogsDir() + if err != nil { + if r.eventLogger != nil { + r.eventLogger.Event("ОШИБКА_ПОЛУЧЕНИЯ_ДИРЕКТОРИИ") + } + return + } + if r.eventLogger != nil { r.eventLogger.Event("ЗАПУЩЕНА_ОЧИСТКА_ЛОГОВ") } // 1. Удаляем старые файлы - deletedByAge := r.cleanByAge() + deletedByAge := r.cleanByAge(dataDir) // 2. Проверяем общий размер и удаляем самые старые если превышен лимит - deletedBySize := r.cleanBySize() + deletedBySize := r.cleanBySize(dataDir) if r.eventLogger != nil && (deletedByAge > 0 || deletedBySize > 0) { r.eventLogger.Event("УДАЛЕНО_ФАЙЛОВ") - log.Printf("🧹 Очистка логов: удалено %d файлов (по возрасту: %d, по размеру: %d)", + log.Printf("Очистка логов: удалено %d файлов (по возрасту: %d, по размеру: %d)", deletedByAge+deletedBySize, deletedByAge, deletedBySize) } } -func (r *Retention) cleanByAge() int { +func (r *Retention) cleanByAge(dataDir string) int { cutoff := time.Now().Add(-time.Duration(r.maxAgeHours) * time.Hour) deleted := 0 // Получаем все .bin файлы - files, err := filepath.Glob(filepath.Join(r.dataDir, "gpio-*.bin")) + files, err := filepath.Glob(filepath.Join(dataDir, "gpio-*.bin")) if err != nil { return 0 } @@ -93,9 +100,9 @@ func (r *Retention) cleanByAge() int { return deleted } -func (r *Retention) cleanBySize() int { +func (r *Retention) cleanBySize(dataDir string) int { // Получаем все файлы - files, err := filepath.Glob(filepath.Join(r.dataDir, "gpio-*.bin")) + files, err := filepath.Glob(filepath.Join(dataDir, "gpio-*.bin")) if err != nil { return 0 } @@ -104,7 +111,7 @@ func (r *Retention) cleanBySize() int { return 0 } - // Сортируем по времени (старые первыми) + // Сортируем по времени (старыми первыми) type fileInfo struct { path string time time.Time @@ -172,33 +179,4 @@ func (r *Retention) parseFilenameTime(filename string) time.Time { hour, _ := time.Parse("15", parts[4]) return time.Date(year.Year(), month.Month(), day.Day(), hour.Hour(), 0, 0, 0, time.Local) -} - -// GetAvailableRange возвращает диапазон доступных данных -func (r *Retention) GetAvailableRange() (oldest, newest time.Time, count int) { - files, err := filepath.Glob(filepath.Join(r.dataDir, "gpio-*.bin")) - if err != nil { - return - } - - count = len(files) - if count == 0 { - return - } - - for _, file := range files { - fileTime := r.parseFilenameTime(file) - if fileTime.IsZero() { - continue - } - - if oldest.IsZero() || fileTime.Before(oldest) { - oldest = fileTime - } - if newest.IsZero() || fileTime.After(newest) { - newest = fileTime - } - } - - return } \ No newline at end of file diff --git a/internal/logger/rotation.go b/internal/logger/rotation.go index ed459a8..93a193c 100644 --- a/internal/logger/rotation.go +++ b/internal/logger/rotation.go @@ -2,27 +2,28 @@ package logger import ( "fmt" - "os" "path/filepath" "sync" "time" ) type RotatingLogger struct { - dataLogger *DataLogger - currentHour int - baseDir string - mu sync.Mutex - eventLogger *EventLogger + dataLogger *DataLogger + currentHour int + baseDir string + mu sync.Mutex + eventLogger *EventLogger } -func NewRotatingLogger(baseDir string, eventLogger *EventLogger) (*RotatingLogger, error) { - if err := os.MkdirAll(baseDir, 0755); err != nil { +func NewRotatingLogger(eventLogger *EventLogger) (*RotatingLogger, error) { + // Получаем директорию для бинарных данных + dataDir, err := GetDataLogsDir() + if err != nil { return nil, err } r := &RotatingLogger{ - baseDir: baseDir, + baseDir: dataDir, eventLogger: eventLogger, } @@ -34,10 +35,10 @@ func NewRotatingLogger(baseDir string, eventLogger *EventLogger) (*RotatingLogge return r, nil } -func (r *RotatingLogger) getFilename(hour int) string { +func (r *RotatingLogger) getFilename() string { now := time.Now() return filepath.Join(r.baseDir, fmt.Sprintf("gpio-%04d-%02d-%02d-%02d.bin", - now.Year(), now.Month(), now.Day(), hour)) + now.Year(), now.Month(), now.Day(), now.Hour())) } func (r *RotatingLogger) rotate() error { @@ -58,16 +59,20 @@ func (r *RotatingLogger) rotate() error { } // Открываем новый - filename := r.getFilename(newHour) + filename := r.getFilename() dataLogger, err := NewDataLogger(filename) if err != nil { - r.eventLogger.Event("ROTATION_FAILED") + if r.eventLogger != nil { + r.eventLogger.Event("ROTATION_FAILED") + } return err } r.dataLogger = dataLogger r.currentHour = newHour - r.eventLogger.Event("ROTATION_COMPLETE") + if r.eventLogger != nil { + r.eventLogger.Event("ROTATION_COMPLETE") + } return nil } diff --git a/scripts/view_logs.sh b/scripts/view_logs.sh index a6fb8a6..c06fea5 100644 --- a/scripts/view_logs.sh +++ b/scripts/view_logs.sh @@ -1,45 +1,36 @@ #!/bin/bash -cd /home/user/temp/golang +LOG_DIR="$HOME/.local/share/gpio-monitoring/logs" echo "=== СТАТИСТИКА ЛОГОВ ===" echo "" -if [ -d logs ]; then +if [ -d "$LOG_DIR" ]; then + echo "Логи в: $LOG_DIR" + echo "" echo "Общая статистика:" - echo " Событий (JSON): $(wc -l < logs/events.log 2>/dev/null || echo 0)" - echo " Событий (Human): $(wc -l < logs/events_human.log 2>/dev/null || echo 0)" - echo " Записей GPIO: $(wc -l < logs/gpio_human.log 2>/dev/null || echo 0)" + echo " Событий (JSON): $(wc -l < $LOG_DIR/events.log 2>/dev/null || echo 0)" + echo " Событий (Human): $(wc -l < $LOG_DIR/events_human.log 2>/dev/null || echo 0)" + echo " Записей GPIO: $(wc -l < $LOG_DIR/gpio_human.log 2>/dev/null || echo 0)" echo "" - echo "Последние 5 предупреждений:" - grep "ВНИМАНИЕ" logs/gpio_human.log 2>/dev/null | tail -5 || echo " Нет предупреждений" + echo "оследние 5 предупреждений:" + grep "ВНИМАНИЕ" $LOG_DIR/gpio_human.log 2>/dev/null | tail -5 || echo " Нет предупреждений" echo "" - echo "Топ-10 по количеству обнаруженных объектов:" - grep "Обнаружение:" logs/gpio_human.log 2>/dev/null | \ - sed -n 's/.* \([0-9]\+\) объектов.*/\1/p' | \ - sort | uniq -c | sort -rn | head -10 | \ - awk '{print " " $2 " объектов: " $1 " раз"}' - echo "" - - echo "Распределение по силе сигнала:" - grep "Обнаружение:" logs/gpio_human.log 2>/dev/null | \ - grep -o "Сила: [А-Яа-я]*" | \ - sort | uniq -c | \ - awk '{print " " $2 " " $3 ": " $1 " раз"}' + echo "Статистика по значениям:" + grep "Значение GPIO:" $LOG_DIR/gpio_human.log 2>/dev/null | awk -F': ' '{print $2}' | awk '{print $1}' | sort | uniq -c | sort -rn | head -10 echo "" echo "💾 Использование диска:" - du -sh logs/ 2>/dev/null + du -sh $LOG_DIR 2>/dev/null echo "" - if ls logs/data/*.bin 1> /dev/null 2>&1; then - echo "Файлы данных:" - ls -lh logs/data/*.bin 2>/dev/null | awk '{print " " $9 ": " $5}' - else - echo "Нет файлов данных" + if ls $LOG_DIR/data/*.bin 1> /dev/null 2>&1; then + echo "📁 Файлы данных:" + ls -lh $LOG_DIR/data/*.bin 2>/dev/null | awk '{print " " $9 ": " $5}' fi else - echo "Директория logs/ не найдена" + echo "❌ Директория логов не найдена: $LOG_DIR" + echo "Запустите сервер для создания логов" fi \ No newline at end of file