Доработка. Использование стандартных путей Linux
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -4,36 +4,39 @@ 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{
|
||||
|
||||
@@ -10,8 +10,9 @@ 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
|
||||
}
|
||||
|
||||
|
||||
85
internal/logger/paths.go
Normal file
85
internal/logger/paths.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
@@ -173,32 +180,3 @@ func (r *Retention) parseFilenameTime(filename string) time.Time {
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user