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
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.
226 lines
4.7 KiB
Go
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))
|
|
}
|
|
}
|