Доработка ротацию лог файлов

This commit is contained in:
Maxim
2026-07-13 09:11:34 +03:00
parent 4509753ac5
commit 1b305500a8
3 changed files with 285 additions and 67 deletions

View File

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