Доработка. Защита от переполнения диска
This commit is contained in:
@@ -192,6 +192,19 @@ func main() {
|
||||
defer dataLogger.Close()
|
||||
eventLogger.Event("DATA_ЛОГЕР_ГОТОВ")
|
||||
|
||||
// 3.5 Retention (автоматическая очистка)
|
||||
retention := logger.NewRetention("logs/data", 48, 5000, eventLogger) // 48 часов, 5 GB
|
||||
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)
|
||||
|
||||
|
||||
204
internal/logger/retention.go
Normal file
204
internal/logger/retention.go
Normal file
@@ -0,0 +1,204 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Retention struct {
|
||||
dataDir string
|
||||
maxAgeHours int // максимальный возраст файлов в часах
|
||||
maxSizeBytes int64 // максимальный размер всех файлов в байтах
|
||||
eventLogger *EventLogger
|
||||
}
|
||||
|
||||
func NewRetention(dataDir string, maxAgeHours int, maxSizeMB int, eventLogger *EventLogger) *Retention {
|
||||
return &Retention{
|
||||
dataDir: dataDir,
|
||||
maxAgeHours: maxAgeHours,
|
||||
maxSizeBytes: int64(maxSizeMB) * 1024 * 1024,
|
||||
eventLogger: eventLogger,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Retention) Start() {
|
||||
// Запускаем проверку каждые 15 минут
|
||||
ticker := time.NewTicker(15 * time.Minute)
|
||||
go func() {
|
||||
for range ticker.C {
|
||||
r.Cleanup()
|
||||
}
|
||||
}()
|
||||
|
||||
// Первая очистка через 1 минуту после запуска
|
||||
time.AfterFunc(1*time.Minute, func() {
|
||||
r.Cleanup()
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Retention) Cleanup() {
|
||||
if r.eventLogger != nil {
|
||||
r.eventLogger.Event("ЗАПУЩЕНА_ОЧИСТКА_ЛОГОВ")
|
||||
}
|
||||
|
||||
// 1. Удаляем старые файлы
|
||||
deletedByAge := r.cleanByAge()
|
||||
|
||||
// 2. Проверяем общий размер и удаляем самые старые если превышен лимит
|
||||
deletedBySize := r.cleanBySize()
|
||||
|
||||
if r.eventLogger != nil && (deletedByAge > 0 || deletedBySize > 0) {
|
||||
r.eventLogger.Event("УДАЛЕНО_ФАЙЛОВ")
|
||||
log.Printf("🧹 Очистка логов: удалено %d файлов (по возрасту: %d, по размеру: %d)",
|
||||
deletedByAge+deletedBySize, deletedByAge, deletedBySize)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Retention) cleanByAge() int {
|
||||
cutoff := time.Now().Add(-time.Duration(r.maxAgeHours) * time.Hour)
|
||||
deleted := 0
|
||||
|
||||
// Получаем все .bin файлы
|
||||
files, err := filepath.Glob(filepath.Join(r.dataDir, "gpio-*.bin"))
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
info, err := os.Stat(file)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Извлекаем время из имени файла
|
||||
fileTime := r.parseFilenameTime(file)
|
||||
if fileTime.IsZero() {
|
||||
fileTime = info.ModTime()
|
||||
}
|
||||
|
||||
if fileTime.Before(cutoff) {
|
||||
if err := os.Remove(file); err == nil {
|
||||
deleted++
|
||||
if r.eventLogger != nil {
|
||||
r.eventLogger.Event("УДАЛЕН_ФАЙЛ_ПО_ВОЗРАСТУ")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return deleted
|
||||
}
|
||||
|
||||
func (r *Retention) cleanBySize() int {
|
||||
// Получаем все файлы
|
||||
files, err := filepath.Glob(filepath.Join(r.dataDir, "gpio-*.bin"))
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
if len(files) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Сортируем по времени (старые первыми)
|
||||
type fileInfo struct {
|
||||
path string
|
||||
time time.Time
|
||||
size int64
|
||||
}
|
||||
|
||||
var fileList []fileInfo
|
||||
var totalSize int64
|
||||
|
||||
for _, file := range files {
|
||||
info, err := os.Stat(file)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
fileTime := r.parseFilenameTime(file)
|
||||
if fileTime.IsZero() {
|
||||
fileTime = info.ModTime()
|
||||
}
|
||||
|
||||
fileList = append(fileList, fileInfo{
|
||||
path: file,
|
||||
time: fileTime,
|
||||
size: info.Size(),
|
||||
})
|
||||
totalSize += info.Size()
|
||||
}
|
||||
|
||||
// Сортируем по времени (старые первые)
|
||||
sort.Slice(fileList, func(i, j int) bool {
|
||||
return fileList[i].time.Before(fileList[j].time)
|
||||
})
|
||||
|
||||
// Удаляем самые старые пока не уложимся в лимит
|
||||
deleted := 0
|
||||
for totalSize > r.maxSizeBytes && len(fileList) > 1 {
|
||||
// Удаляем самый старый файл
|
||||
oldest := fileList[0]
|
||||
if err := os.Remove(oldest.path); err == nil {
|
||||
totalSize -= oldest.size
|
||||
deleted++
|
||||
fileList = fileList[1:]
|
||||
if r.eventLogger != nil {
|
||||
r.eventLogger.Event("УДАЛЕН_ФАЙЛ_ПО_РАЗМЕРУ")
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return deleted
|
||||
}
|
||||
|
||||
func (r *Retention) parseFilenameTime(filename string) time.Time {
|
||||
base := filepath.Base(filename)
|
||||
// Формат: gpio-2026-06-09-11.bin
|
||||
parts := strings.Split(strings.TrimSuffix(base, ".bin"), "-")
|
||||
if len(parts) != 5 {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
year, _ := time.Parse("2006", parts[1])
|
||||
month, _ := time.Parse("01", parts[2])
|
||||
day, _ := time.Parse("02", parts[3])
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user