Доработка ротацию лог файлов
This commit is contained in:
@@ -10,6 +10,10 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// minKeepFiles — сколько самых свежих файлов всегда сохраняем, даже если
|
||||
// возраст или размер говорят удалить. Защита от полного стирания директории.
|
||||
const minKeepFiles = 2
|
||||
|
||||
type Retention struct {
|
||||
maxAgeHours int
|
||||
maxSizeBytes int64
|
||||
@@ -103,58 +107,21 @@ func (r *Retention) Cleanup() {
|
||||
}
|
||||
}
|
||||
|
||||
// cleanByAge возвращает количество удаленных файлов и их общий размер в байтах
|
||||
func (r *Retention) cleanByAge(dataDir string) (int, int64) {
|
||||
cutoff := time.Now().Add(-time.Duration(r.maxAgeHours) * time.Hour)
|
||||
deleted := 0
|
||||
var deletedBytes int64
|
||||
|
||||
// Получаем все .bin файлы
|
||||
files, err := filepath.Glob(filepath.Join(dataDir, "gpio-*.bin"))
|
||||
if err != nil {
|
||||
return 0, 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++
|
||||
deletedBytes += info.Size()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return deleted, deletedBytes
|
||||
// fileInfo описывает один лог-файл: путь, время из имени (или ModTime как
|
||||
// запасной вариант), реальное время последней записи и размер.
|
||||
type fileInfo struct {
|
||||
path string
|
||||
time time.Time
|
||||
modTime time.Time
|
||||
size int64
|
||||
}
|
||||
|
||||
// cleanBySize возвращает количество удаленных файлов и их общий размер в байтах
|
||||
func (r *Retention) cleanBySize(dataDir string) (int, int64) {
|
||||
// Получаем все файлы
|
||||
// collectFiles возвращает все gpio-*.bin, отсортированные от старых к новым
|
||||
// (FIFO-порядок), и их суммарный размер в байтах.
|
||||
func (r *Retention) collectFiles(dataDir string) ([]fileInfo, int64) {
|
||||
files, err := filepath.Glob(filepath.Join(dataDir, "gpio-*.bin"))
|
||||
if err != nil {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
if len(files) == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// Сортируем по времени (старыми первыми)
|
||||
type fileInfo struct {
|
||||
path string
|
||||
time time.Time
|
||||
size int64
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
var fileList []fileInfo
|
||||
@@ -166,40 +133,82 @@ func (r *Retention) cleanBySize(dataDir string) (int, int64) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Время из имени файла; если распарсить не удалось — берём ModTime.
|
||||
fileTime := r.parseFilenameTime(file)
|
||||
if fileTime.IsZero() {
|
||||
fileTime = info.ModTime()
|
||||
}
|
||||
|
||||
fileList = append(fileList, fileInfo{
|
||||
path: file,
|
||||
time: fileTime,
|
||||
size: info.Size(),
|
||||
path: file,
|
||||
time: fileTime,
|
||||
modTime: info.ModTime(),
|
||||
size: info.Size(),
|
||||
})
|
||||
totalSize += info.Size()
|
||||
}
|
||||
|
||||
// Сортируем по времени (старые первые)
|
||||
// Сортируем по времени (старые первые) — FIFO.
|
||||
sort.Slice(fileList, func(i, j int) bool {
|
||||
return fileList[i].time.Before(fileList[j].time)
|
||||
})
|
||||
|
||||
// Удаляем самые старые пока не уложимся в лимит
|
||||
return fileList, totalSize
|
||||
}
|
||||
|
||||
// cleanByAge удаляет старые файлы по FIFO: с самого старого края, пока файл
|
||||
// старше cutoff И по имени, И по ModTime, и пока не упрёмся в minKeepFiles.
|
||||
// Возвращает количество удалённых файлов и их общий размер в байтах.
|
||||
func (r *Retention) cleanByAge(dataDir string) (int, int64) {
|
||||
cutoff := time.Now().Add(-time.Duration(r.maxAgeHours) * time.Hour)
|
||||
|
||||
files, _ := r.collectFiles(dataDir)
|
||||
|
||||
deleted := 0
|
||||
var deletedBytes int64
|
||||
|
||||
// Сохраняем как минимум 1 файл
|
||||
for totalSize > r.maxSizeBytes && len(fileList) > 1 {
|
||||
// Удаляем самый старый файл
|
||||
oldest := fileList[0]
|
||||
if err := os.Remove(oldest.path); err == nil {
|
||||
totalSize -= oldest.size
|
||||
deletedBytes += oldest.size
|
||||
deleted++
|
||||
fileList = fileList[1:]
|
||||
} else {
|
||||
for len(files) > minKeepFiles {
|
||||
f := files[0]
|
||||
|
||||
// Возраст — это цель, а не гильотина. Файл удаляем только если он старше
|
||||
// cutoff и по имени, и по реальному времени записи. Это защищает от
|
||||
// скачка часов (после NTP на устройстве без RTC): свежий по ModTime
|
||||
// файл со «старым» именем не будет снесён.
|
||||
if !f.time.Before(cutoff) || !f.modTime.Before(cutoff) {
|
||||
// Список отсортирован от старых к новым — дальше только моложе.
|
||||
break
|
||||
}
|
||||
|
||||
if err := os.Remove(f.path); err != nil {
|
||||
break
|
||||
}
|
||||
deleted++
|
||||
deletedBytes += f.size
|
||||
files = files[1:]
|
||||
}
|
||||
|
||||
return deleted, deletedBytes
|
||||
}
|
||||
|
||||
// cleanBySize удаляет самые старые файлы, пока суммарный размер превышает лимит,
|
||||
// сохраняя как минимум minKeepFiles файлов.
|
||||
// Возвращает количество удалённых файлов и их общий размер в байтах.
|
||||
func (r *Retention) cleanBySize(dataDir string) (int, int64) {
|
||||
fileList, totalSize := r.collectFiles(dataDir)
|
||||
|
||||
deleted := 0
|
||||
var deletedBytes int64
|
||||
|
||||
for totalSize > r.maxSizeBytes && len(fileList) > minKeepFiles {
|
||||
// Удаляем самый старый файл.
|
||||
oldest := fileList[0]
|
||||
if err := os.Remove(oldest.path); err != nil {
|
||||
break
|
||||
}
|
||||
totalSize -= oldest.size
|
||||
deletedBytes += oldest.size
|
||||
deleted++
|
||||
fileList = fileList[1:]
|
||||
}
|
||||
|
||||
return deleted, deletedBytes
|
||||
@@ -213,10 +222,22 @@ func (r *Retention) parseFilenameTime(filename string) time.Time {
|
||||
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])
|
||||
year, err := time.Parse("2006", parts[1])
|
||||
if err != nil {
|
||||
return time.Time{}
|
||||
}
|
||||
month, err := time.Parse("01", parts[2])
|
||||
if err != nil {
|
||||
return time.Time{}
|
||||
}
|
||||
day, err := time.Parse("02", parts[3])
|
||||
if err != nil {
|
||||
return time.Time{}
|
||||
}
|
||||
hour, err := time.Parse("15", parts[4])
|
||||
if err != nil {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
return time.Date(year.Year(), month.Month(), day.Day(), hour.Hour(), 0, 0, 0, time.Local)
|
||||
}
|
||||
Reference in New Issue
Block a user