Доработка. Использование стандартных путей 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

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