Доработка. Использование стандартных путей Linux
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user