Files
go-service/internal/logger/retention.go

222 lines
5.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package logger
import (
"fmt"
"log"
"os"
"path/filepath"
"sort"
"strings"
"time"
)
type Retention struct {
maxAgeHours int
maxSizeBytes int64
eventLogger *EventLogger
stopCh chan struct{}
}
func NewRetention(maxAgeHours int, maxSizeMB int, eventLogger *EventLogger) *Retention {
return &Retention{
maxAgeHours: maxAgeHours,
maxSizeBytes: int64(maxSizeMB) * 1024 * 1024,
eventLogger: eventLogger,
stopCh: make(chan struct{}),
}
}
func (r *Retention) Start() {
// Запускаем проверку каждые 15 минут
r.StartWithInterval(15 * time.Minute)
}
func (r *Retention) StartWithInterval(interval time.Duration) {
// Запускаем проверку с указанным интервалом
ticker := time.NewTicker(interval)
go func() {
for {
select {
case <-ticker.C:
r.Cleanup()
case <-r.stopCh:
ticker.Stop()
return
}
}
}()
// Первая очистка через 1 минуту после запуска
time.AfterFunc(1*time.Minute, func() {
r.Cleanup()
})
}
func (r *Retention) Stop() {
close(r.stopCh)
}
func (r *Retention) Cleanup() {
// Если оба лимита отключены, ничего не делаем
if r.maxAgeHours <= 0 && r.maxSizeBytes <= 0 {
return
}
// Получаем директорию с данными
dataDir, err := GetDataLogsDir()
if err != nil {
if r.eventLogger != nil {
r.eventLogger.Event("ОШИБКАОЛУЧЕНИЯ_ДИРЕКТОРИИ")
}
return
}
// 1. Удаляем старые файлы (если включено)
deletedByAge := 0
var deletedByAgeBytes int64
if r.maxAgeHours > 0 {
deletedByAge, deletedByAgeBytes = r.cleanByAge(dataDir)
}
// 2. Проверяем общий размер и удаляем самые старые если превышен лимит (если включено)
deletedBySize := 0
var deletedBySizeBytes int64
if r.maxSizeBytes > 0 {
deletedBySize, deletedBySizeBytes = r.cleanBySize(dataDir)
}
totalDeleted := deletedByAge + deletedBySize
totalDeletedBytes := deletedByAgeBytes + deletedBySizeBytes
// Логируем ТОЛЬКО если были удаления
if totalDeleted > 0 {
// Логируем суммарное событие
if r.eventLogger != nil {
eventMsg := fmt.Sprintf("ОЧИСТКАОГОВ_УДАЛЕНО_%d_ФАЙЛОВ_%d_MB",
totalDeleted, totalDeletedBytes/(1024*1024))
r.eventLogger.Event(eventMsg)
}
// Пишем в консоль для отладки
log.Printf("Очистка логов: удалено %d файлов (%.2f MB) [по возрасту: %d, по размеру: %d]",
totalDeleted, float64(totalDeletedBytes)/(1024*1024), deletedByAge, deletedBySize)
}
}
// 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
}
// cleanBySize возвращает количество удаленных файлов и их общий размер в байтах
func (r *Retention) cleanBySize(dataDir string) (int, 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
}
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
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 {
break
}
}
return deleted, deletedBytes
}
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)
}