Files
EvoBGP/internal/runtimelogs/policy.go
T
Denozordec db75126bea
CI / changes (push) Successful in 9s
CI / commitlint (push) Has been skipped
CI / openapi (push) Successful in 26s
CI / web (push) Successful in 33s
CI / go (push) Successful in 56s
CI / bird2 (push) Successful in 14s
CI / release (push) Successful in 20s
feat(runtime-logs): enhance auto-cleanup features and documentation
Added new endpoints for estimating and executing runtime log auto-cleanup based on tenant settings. Introduced configuration options for auto-cleanup policies, including scheduling and file size limits. Updated the API documentation and UI components to reflect these changes, improving user interaction with runtime log management. Enhanced error handling and added new UI elements for better visibility of audit logs and cleanup actions.
2026-06-12 22:44:39 +07:00

226 lines
4.7 KiB
Go

package runtimelogs
import (
"fmt"
"strconv"
"strings"
"evobgp/internal/store"
"github.com/robfig/cron/v3"
)
// global_settings keys for runtime log auto-cleanup.
const (
KeyAutoEnabled = "runtime_logs_auto_enabled"
KeyMaxFileMB = "runtime_logs_max_file_mb"
KeyAutoSchedule = "runtime_logs_auto_schedule"
KeyAutoMode = "runtime_logs_auto_mode"
DefaultMaxFileMB = 128
DefaultSchedule = "0 */6 * * *"
)
// AutoPolicy is tenant KV configuration for scheduled FS log cleanup.
type AutoPolicy struct {
Enabled bool
MaxFileBytes int64
Schedule string
Mode string
}
// DefaultAutoPolicy returns policy defaults when settings are unset.
func DefaultAutoPolicy() AutoPolicy {
return AutoPolicy{
Enabled: false,
MaxFileBytes: int64(DefaultMaxFileMB) * 1024 * 1024,
Schedule: DefaultSchedule,
Mode: store.RuntimeLogCleanupTruncate,
}
}
// PolicyFromSettings reads auto-cleanup policy from tenant global_settings KV.
func PolicyFromSettings(settings map[string]any) AutoPolicy {
p := DefaultAutoPolicy()
if settings == nil {
return p
}
if v, ok := settings[KeyAutoEnabled]; ok {
p.Enabled = parseBoolSetting(v)
}
if mb := parseIntSetting(settings[KeyMaxFileMB]); mb > 0 {
p.MaxFileBytes = int64(ClampMaxFileMB(mb)) * 1024 * 1024
}
if s := parseStringSetting(settings[KeyAutoSchedule]); s != "" {
p.Schedule = s
}
if m := parseStringSetting(settings[KeyAutoMode]); store.ValidRuntimeLogCleanupAction(m) {
p.Mode = m
}
return p
}
// ClampMaxFileMB normalizes max file size to 1..512 MiB.
func ClampMaxFileMB(mb int) int {
if mb <= 0 {
return DefaultMaxFileMB
}
if mb < 1 {
return 1
}
if mb > 512 {
return 512
}
return mb
}
// ValidAutoSchedule reports whether schedule is a valid 5-field UTC cron expression.
func ValidAutoSchedule(schedule string) bool {
schedule = strings.TrimSpace(schedule)
if schedule == "" {
return false
}
parser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
_, err := parser.Parse(schedule)
return err == nil
}
// ValidateRuntimeLogsSettingsPatch normalizes and validates runtime log settings in a PATCH body.
// Returns false if any present key is invalid.
func ValidateRuntimeLogsSettingsPatch(body map[string]any) bool {
if raw, ok := body[KeyAutoEnabled]; ok && raw != nil {
v, ok := normalizeBoolSetting(raw)
if !ok {
return false
}
body[KeyAutoEnabled] = v
}
if raw, ok := body[KeyMaxFileMB]; ok && raw != nil {
mb, ok := parsePatchInt(raw)
if !ok {
return false
}
mb = ClampMaxFileMB(mb)
body[KeyMaxFileMB] = mb
}
if raw, ok := body[KeyAutoSchedule]; ok && raw != nil {
s := parseStringSetting(raw)
if !ValidAutoSchedule(s) {
return false
}
body[KeyAutoSchedule] = s
}
if raw, ok := body[KeyAutoMode]; ok && raw != nil {
m := parseStringSetting(raw)
if !store.ValidRuntimeLogCleanupAction(m) {
return false
}
body[KeyAutoMode] = m
}
return true
}
func parseBoolSetting(v any) bool {
switch x := v.(type) {
case bool:
return x
case string:
s := strings.TrimSpace(strings.ToLower(x))
return s == "1" || s == "true" || s == "yes"
case float64:
return x != 0
case int:
return x != 0
case int64:
return x != 0
default:
return false
}
}
func normalizeBoolSetting(v any) (bool, bool) {
switch x := v.(type) {
case bool:
return x, true
case string:
s := strings.TrimSpace(strings.ToLower(x))
switch s {
case "1", "true", "yes":
return true, true
case "0", "false", "no":
return false, true
default:
return false, false
}
case float64:
if x != 0 && x != 1 {
return false, false
}
return x != 0, true
case int:
if x != 0 && x != 1 {
return false, false
}
return x != 0, true
case int64:
if x != 0 && x != 1 {
return false, false
}
return x != 0, true
default:
return false, false
}
}
func parseIntSetting(v any) int {
switch x := v.(type) {
case float64:
return int(x)
case int:
return x
case int64:
return int(x)
case string:
n, err := strconv.Atoi(strings.TrimSpace(x))
if err == nil {
return n
}
}
return 0
}
func parsePatchInt(v any) (int, bool) {
switch x := v.(type) {
case float64:
if x != float64(int(x)) {
return 0, false
}
n := int(x)
return n, n >= 1 && n <= 512
case int:
return x, x >= 1 && x <= 512
case int64:
n := int(x)
return n, n >= 1 && n <= 512
case string:
n, err := strconv.Atoi(strings.TrimSpace(x))
if err != nil {
return 0, false
}
return n, n >= 1 && n <= 512
default:
return 0, false
}
}
func parseStringSetting(v any) string {
switch x := v.(type) {
case string:
return strings.TrimSpace(x)
default:
if v == nil {
return ""
}
return strings.TrimSpace(fmt.Sprint(v))
}
}