Доработка. Использование стандартных путей Linux

This commit is contained in:
Maxim
2026-06-09 16:02:47 +03:00
parent d84856ba3a
commit 730facdf59
7 changed files with 186 additions and 126 deletions

View File

@@ -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)

View File

@@ -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
}
}

View File

@@ -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()
}
}

85
internal/logger/paths.go Normal file
View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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