Repository: dragonflyoss/dragonfly

Issue:
Make log rotation MaxSize, MaxAge, and MaxBackups configurable
### Feature request:

Make log rotation MaxSize, MaxAge, and MaxBackups configurable. 

### Use case:

The current hard-coded settings can lead to up to 20GB of disk space used per log file. It is reasonable to expect that some users may want to enable verbose mode, but still not allow so much space to be used.

Hints from issue comments:
@gaius-qi: @embroede I think this is a reasonable feature. Can you provide a PR to complete this feature?@embroede: @gaius-qi Sure I can do that :+1: @gaius-qi: @embroede Thanks!@embroede: @gaius-qi  PR is open. As v2.2.0 might not be out for a while, and contains many changes, would we be able to backport this change on top of v2.0.10, to make v2.0.11 ?@embroede: @gaius-qi @jim3ma Thanks for merging. Is there any way this can be backported to v2.0.10 (maybe as v2.0.11)? 

Solution patch:
diff --git a/client/config/constants.go b/client/config/constants.go
index 8f6a931b..33b8599b 100644
--- a/client/config/constants.go
+++ b/client/config/constants.go
@@ -93,3 +93,14 @@ const (
 	// DefaultProbeInterval is the default interval of probing host.
 	DefaultProbeInterval = 20 * time.Minute
 )
+
+const (
+	// DefaultLogRotateMaxSize is the default maximum size in megabytes of log files before rotation.
+	DefaultLogRotateMaxSize = 1024
+
+	// DefaultLogRotateMaxAge is the default number of days to retain old log files.
+	DefaultLogRotateMaxAge = 7
+
+	// DefaultLogRotateMaxBackups is the default number of old log files to keep.
+	DefaultLogRotateMaxBackups = 20
+)
diff --git a/client/config/dfcache.go b/client/config/dfcache.go
index 4ec89098..bad0adb8 100644
--- a/client/config/dfcache.go
+++ b/client/config/dfcache.go
@@ -52,6 +52,15 @@ type CacheOption struct {
 	// LogDir is log directory of dfcache.
 	LogDir string `yaml:"logDir,omitempty" mapstructure:"logDir,omitempty"`
 
+	// Maximum size in megabytes of log files before rotation (default: 1024)
+	LogMaxSize int `yaml:"logMaxSize" mapstructure:"logMaxSize"`
+
+	// Maximum number of days to retain old log files (default: 7)
+	LogMaxAge int `yaml:"logMaxAge" mapstructure:"logMaxAge"`
+
+	// Maximum number of old log files to keep (default: 20)
+	LogMaxBackups int `yaml:"logMaxBackups" mapstructure:"logMaxBackups"`
+
 	// WorkHome is working directory of dfcache.
 	WorkHome string `yaml:"workHome,omitempty" mapstructure:"workHome,omitempty"`
 
diff --git a/client/config/dfget.go b/client/config/dfget.go
index cd9a026d..27213e1e 100644
--- a/client/config/dfget.go
+++ b/client/config/dfget.go
@@ -100,6 +100,15 @@ type ClientOption struct {
 	// LogDir is log directory of dfget.
 	LogDir string `yaml:"logDir,omitempty" mapstructure:"logDir,omitempty"`
 
+	// Maximum size in megabytes of log files before rotation (default: 1024)
+	LogMaxSize int `yaml:"logMaxSize" mapstructure:"logMaxSize"`
+
+	// Maximum number of days to retain old log files (default: 7)
+	LogMaxAge int `yaml:"logMaxAge" mapstructure:"logMaxAge"`
+
+	// Maximum number of old log files to keep (default: 20)
+	LogMaxBackups int `yaml:"logMaxBackups" mapstructure:"logMaxBackups"`
+
 	// DataDir is data directory of dfget.
 	DataDir string `yaml:"dataDir,omitempty" mapstructure:"dataDir,omitempty"`
 
diff --git a/client/config/dfget_linux.go b/client/config/dfget_linux.go
index 4f26b81f..e6214936 100644
--- a/client/config/dfget_linux.go
+++ b/client/config/dfget_linux.go
@@ -45,4 +45,7 @@ var dfgetConfig = ClientOption{
 	ShowProgress:      false,
 	Recursive:         false,
 	RecursiveLevel:    5,
+	LogMaxSize:        DefaultLogRotateMaxSize,
+	LogMaxAge:         DefaultLogRotateMaxAge,
+	LogMaxBackups:     DefaultLogRotateMaxBackups,
 }
diff --git a/client/config/peerhost.go b/client/config/peerhost.go
index 35605836..56ec8367 100644
--- a/client/config/peerhost.go
+++ b/client/config/peerhost.go
@@ -52,15 +52,18 @@ type DaemonOption struct {
 	GCInterval util.Duration `mapstructure:"gcInterval" yaml:"gcInterval"`
 	Metrics    string        `mapstructure:"metrics" yaml:"metrics"`
 
-	WorkHome     string `mapstructure:"workHome" yaml:"workHome"`
-	WorkHomeMode uint32 `mapstructure:"workHomeMode" yaml:"workHomeMode"`
-	CacheDir     string `mapstructure:"cacheDir" yaml:"cacheDir"`
-	CacheDirMode uint32 `mapstructure:"cacheDirMode" yaml:"cacheDirMode"`
-	LogDir       string `mapstructure:"logDir" yaml:"logDir"`
-	PluginDir    string `mapstructure:"pluginDir" yaml:"pluginDir"`
-	DataDir      string `mapstructure:"dataDir" yaml:"dataDir"`
-	DataDirMode  uint32 `mapstructure:"dataDirMode" yaml:"dataDirMode"`
-	KeepStorage  bool   `mapstructure:"keepStorage" yaml:"keepStorage"`
+	WorkHome      string `mapstructure:"workHome" yaml:"workHome"`
+	WorkHomeMode  uint32 `mapstructure:"workHomeMode" yaml:"workHomeMode"`
+	CacheDir      string `mapstructure:"cacheDir" yaml:"cacheDir"`
+	CacheDirMode  uint32 `mapstructure:"cacheDirMode" yaml:"cacheDirMode"`
+	LogDir        string `mapstructure:"logDir" yaml:"logDir"`
+	LogMaxSize    int    `yaml:"logMaxSize" mapstructure:"logMaxSize"`
+	LogMaxAge     int    `yaml:"logMaxAge" mapstructure:"logMaxAge"`
+	LogMaxBackups int    `yaml:"logMaxBackups" mapstructure:"logMaxBackups"`
+	PluginDir     string `mapstructure:"pluginDir" yaml:"pluginDir"`
+	DataDir       string `mapstructure:"dataDir" yaml:"dataDir"`
+	DataDirMode   uint32 `mapstructure:"dataDirMode" yaml:"dataDirMode"`
+	KeepStorage   bool   `mapstructure:"keepStorage" yaml:"keepStorage"`
 
 	Security        GlobalSecurityOption  `mapstructure:"security" yaml:"security"`
 	Scheduler       SchedulerOption       `mapstructure:"scheduler" yaml:"scheduler"`
diff --git a/client/config/peerhost_darwin.go b/client/config/peerhost_darwin.go
index b8218090..84929558 100644
--- a/client/config/peerhost_darwin.go
+++ b/client/config/peerhost_darwin.go
@@ -186,5 +186,8 @@ var peerHostConfig = func() *DaemonOption {
 				Interval: DefaultProbeInterval,
 			},
 		},
+		LogMaxSize:    DefaultLogRotateMaxSize,
+		LogMaxAge:     DefaultLogRotateMaxAge,
+		LogMaxBackups: DefaultLogRotateMaxBackups,
 	}
 }
diff --git a/client/config/peerhost_linux.go b/client/config/peerhost_linux.go
index 4d3e8084..3c2bedb0 100644
--- a/client/config/peerhost_linux.go
+++ b/client/config/peerhost_linux.go
@@ -186,5 +186,8 @@ var peerHostConfig = func() *DaemonOption {
 				Interval: DefaultProbeInterval,
 			},
 		},
+		LogMaxSize:    DefaultLogRotateMaxSize,
+		LogMaxAge:     DefaultLogRotateMaxAge,
+		LogMaxBackups: DefaultLogRotateMaxBackups,
 	}
 }
diff --git a/client/daemon/peer/peertask_bitmap.go b/client/daemon/peer/peertask_bitmap.go
index f9e961e1..c73a7549 100644
--- a/client/daemon/peer/peertask_bitmap.go
+++ b/client/daemon/peer/peertask_bitmap.go
@@ -17,52 +17,50 @@
 package peer
 
 import (
-	"go.uber.org/atomic"
+	"sync"
+
+	"github.com/bits-and-blooms/bitset"
 )
 
 type Bitmap struct {
-	bits    []byte
-	cap     int32
-	settled atomic.Int32
+	bits *bitset.BitSet
+	mu   sync.RWMutex
 }
 
 func NewBitmap() *Bitmap {
 	return &Bitmap{
-		bits: make([]byte, 8),
-		cap:  8 * 8,
+		bits: bitset.New(8),
 	}
 }
 
 func NewBitmapWithCap(c int32) *Bitmap {
 	return &Bitmap{
-		bits: make([]byte, c),
-		cap:  c * 8,
+		bits: bitset.New(uint(c)),
 	}
 }
 
 func (b *Bitmap) IsSet(i int32) bool {
-	if i >= b.cap {
-		return false
-	}
-	return b.bits[i/8]&(1<<uint(7-i%8)) != 0
+	b.mu.RLock()
+	defer b.mu.RUnlock()
+	return b.bits.Test(uint(i))
 }
 
 func (b *Bitmap) Set(i int32) {
-	for i >= b.cap {
-		b.bits = append(b.bits, make([]byte, b.cap/8)...)
-		b.cap *= 2
-	}
-	b.settled.Inc()
-	b.bits[i/8] |= 1 << uint(7-i%8)
+	b.mu.Lock()
+	defer b.mu.Unlock()
+	b.bits.Set(uint(i))
 }
 
 func (b *Bitmap) Clean(i int32) {
-	b.settled.Dec()
-	b.bits[i/8] ^= 1 << uint(7-i%8)
+	b.mu.Lock()
+	defer b.mu.Unlock()
+	b.bits.Clear(uint(i))
 }
 
 func (b *Bitmap) Settled() int32 {
-	return b.settled.Load()
+	b.mu.RLock()
+	defer b.mu.RUnlock()
+	return int32(b.bits.Count())
 }
 
 func (b *Bitmap) Sets(xs ...int32) {
diff --git a/cmd/dfcache/cmd/root.go b/cmd/dfcache/cmd/root.go
index 493f256b..d0f3d62e 100644
--- a/cmd/dfcache/cmd/root.go
+++ b/cmd/dfcache/cmd/root.go
@@ -136,8 +136,13 @@ func runDfcacheSubcmd(cmdName string, args []string) error {
 		return err
 	}
 
+	rotateConfig := logger.LogRotateConfig{
+		MaxSize:    dfcacheConfig.LogMaxSize,
+		MaxAge:     dfcacheConfig.LogMaxAge,
+		MaxBackups: dfcacheConfig.LogMaxBackups}
+
 	// Initialize logger
-	if err := logger.InitDfcache(dfcacheConfig.Console, d.LogDir()); err != nil {
+	if err := logger.InitDfcache(dfcacheConfig.Console, d.LogDir(), rotateConfig); err != nil {
 		return fmt.Errorf("init client dfcache logger: %w", err)
 	}
 	logger.Infof("version:\n%s", version.Version())
diff --git a/cmd/dfget/cmd/daemon.go b/cmd/dfget/cmd/daemon.go
index 0f3ee809..3bc307ee 100644
--- a/cmd/dfget/cmd/daemon.go
+++ b/cmd/dfget/cmd/daemon.go
@@ -71,8 +71,13 @@ it supports container engine, wget and other downloading tools through proxy fun
 			return err
 		}
 
+		rotateConfig := logger.LogRotateConfig{
+			MaxSize:    cfg.LogMaxSize,
+			MaxAge:     cfg.LogMaxAge,
+			MaxBackups: cfg.LogMaxBackups}
+
 		// Initialize logger
-		if err := logger.InitDaemon(cfg.Verbose, cfg.Console, d.LogDir()); err != nil {
+		if err := logger.InitDaemon(cfg.Verbose, cfg.Console, d.LogDir(), rotateConfig); err != nil {
 			return fmt.Errorf("init client daemon logger: %w", err)
 		}
 		logger.RedirectStdoutAndStderr(cfg.Console, path.Join(d.LogDir(), types.DaemonName))
diff --git a/cmd/dfget/cmd/root.go b/cmd/dfget/cmd/root.go
index ac147924..e7720185 100644
--- a/cmd/dfget/cmd/root.go
+++ b/cmd/dfget/cmd/root.go
@@ -85,8 +85,13 @@ var rootCmd = &cobra.Command{
 			return err
 		}
 
+		rotateConfig := logger.LogRotateConfig{
+			MaxSize:    dfgetConfig.LogMaxSize,
+			MaxAge:     dfgetConfig.LogMaxAge,
+			MaxBackups: dfgetConfig.LogMaxBackups}
+
 		// Initialize logger
-		if err := logger.InitDfget(dfgetConfig.Verbose, dfgetConfig.Console, d.LogDir()); err != nil {
+		if err := logger.InitDfget(dfgetConfig.Verbose, dfgetConfig.Console, d.LogDir(), rotateConfig); err != nil {
 			return fmt.Errorf("init client dfget logger: %w", err)
 		}
 
diff --git a/cmd/manager/cmd/root.go b/cmd/manager/cmd/root.go
index e42951a1..6d91cfff 100644
--- a/cmd/manager/cmd/root.go
+++ b/cmd/manager/cmd/root.go
@@ -62,8 +62,13 @@ for managing schedulers and seed peers, offering http apis and portal, etc.`,
 			return err
 		}
 
+		rotateConfig := logger.LogRotateConfig{
+			MaxSize:    cfg.Server.LogMaxSize,
+			MaxAge:     cfg.Server.LogMaxAge,
+			MaxBackups: cfg.Server.LogMaxBackups}
+
 		// Initialize logger.
-		if err := logger.InitManager(cfg.Verbose, cfg.Console, d.LogDir()); err != nil {
+		if err := logger.InitManager(cfg.Verbose, cfg.Console, d.LogDir(), rotateConfig); err != nil {
 			return fmt.Errorf("init manager logger: %w", err)
 		}
 		logger.RedirectStdoutAndStderr(cfg.Console, path.Join(d.LogDir(), types.ManagerName))
diff --git a/cmd/scheduler/cmd/root.go b/cmd/scheduler/cmd/root.go
index 20ab1a7f..791c5c51 100644
--- a/cmd/scheduler/cmd/root.go
+++ b/cmd/scheduler/cmd/root.go
@@ -66,8 +66,13 @@ generate and maintain a P2P network during the download process, and push suitab
 			return err
 		}
 
+		rotateConfig := logger.LogRotateConfig{
+			MaxSize:    cfg.Server.LogMaxSize,
+			MaxAge:     cfg.Server.LogMaxAge,
+			MaxBackups: cfg.Server.LogMaxBackups}
+
 		// Initialize logger.
-		if err := logger.InitScheduler(cfg.Verbose, cfg.Console, d.LogDir()); err != nil {
+		if err := logger.InitScheduler(cfg.Verbose, cfg.Console, d.LogDir(), rotateConfig); err != nil {
 			return fmt.Errorf("init scheduler logger: %w", err)
 		}
 		logger.RedirectStdoutAndStderr(cfg.Console, path.Join(d.LogDir(), types.SchedulerName))
diff --git a/cmd/trainer/cmd/root.go b/cmd/trainer/cmd/root.go
index 73471da2..e617d730 100644
--- a/cmd/trainer/cmd/root.go
+++ b/cmd/trainer/cmd/root.go
@@ -65,9 +65,13 @@ preprocessing original record data, establing datasets and training machine lear
 		if err != nil {
 			return err
 		}
+		rotateConfig := logger.LogRotateConfig{
+			MaxSize:    cfg.Server.LogMaxSize,
+			MaxAge:     cfg.Server.LogMaxAge,
+			MaxBackups: cfg.Server.LogMaxBackups}
 
 		// Initialize logger.
-		if err := logger.InitTrainer(cfg.Verbose, cfg.Console, d.LogDir()); err != nil {
+		if err := logger.InitTrainer(cfg.Verbose, cfg.Console, d.LogDir(), rotateConfig); err != nil {
 			return fmt.Errorf("init trainer logger: %w", err)
 		}
 		logger.RedirectStdoutAndStderr(cfg.Console, path.Join(d.LogDir(), types.SchedulerName))
diff --git a/go.mod b/go.mod
index a1166f18..aac86dbf 100644
--- a/go.mod
+++ b/go.mod
@@ -3,14 +3,14 @@ module d7y.io/dragonfly/v2
 go 1.21
 
 require (
-	d7y.io/api/v2 v2.0.95
+	d7y.io/api/v2 v2.0.97
 	github.com/MysteriousPotato/go-lockable v1.0.0
 	github.com/RichardKnop/machinery v1.10.6
 	github.com/Showmax/go-fqdn v1.0.0
 	github.com/VividCortex/mysqlerr v1.0.0
 	github.com/aliyun/aliyun-oss-go-sdk v3.0.1+incompatible
 	github.com/appleboy/gin-jwt/v2 v2.9.2
-	github.com/aws/aws-sdk-go v1.49.17
+	github.com/aws/aws-sdk-go v1.50.25
 	github.com/bits-and-blooms/bitset v1.13.0
 	github.com/casbin/casbin/v2 v2.81.0
 	github.com/casbin/gorm-adapter/v3 v3.5.0
@@ -19,7 +19,7 @@ require (
 	github.com/deckarep/golang-set/v2 v2.6.0
 	github.com/distribution/distribution/v3 v3.0.0-20220620080156-3e4f8a0ab147
 	github.com/docker/distribution v2.8.3+incompatible
-	github.com/docker/docker v24.0.7+incompatible
+	github.com/docker/docker v25.0.3+incompatible
 	github.com/docker/go-connections v0.5.0
 	github.com/docker/go-units v0.4.0
 	github.com/gaius-qi/ping v1.0.0
@@ -30,7 +30,7 @@ require (
 	github.com/gin-gonic/gin v1.9.1
 	github.com/go-echarts/statsview v0.3.4
 	github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a
-	github.com/go-playground/validator/v10 v10.17.0
+	github.com/go-playground/validator/v10 v10.18.0
 	github.com/go-redis/cache/v8 v8.4.3
 	github.com/go-redis/redis/v8 v8.11.5
 	github.com/go-redis/redismock/v8 v8.11.5
@@ -85,13 +85,13 @@ require (
 	golang.org/x/sys v0.17.0
 	golang.org/x/time v0.5.0
 	google.golang.org/api v0.165.0
-	google.golang.org/grpc v1.61.0
+	google.golang.org/grpc v1.62.0
 	google.golang.org/protobuf v1.32.0
 	gopkg.in/natefinch/lumberjack.v2 v2.0.0
 	gopkg.in/yaml.v3 v3.0.1
-	gorm.io/driver/mysql v1.5.2
+	gorm.io/driver/mysql v1.5.4
 	gorm.io/driver/postgres v1.5.2
-	gorm.io/gorm v1.25.5
+	gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde
 	gorm.io/plugin/soft_delete v1.2.1
 	k8s.io/component-base v0.29.2
 	logur.dev/adapter/zap v0.5.0
@@ -105,7 +105,6 @@ require (
 	cloud.google.com/go/compute/metadata v0.2.3 // indirect
 	cloud.google.com/go/iam v1.1.5 // indirect
 	cloud.google.com/go/pubsub v1.34.0 // indirect
-	github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
 	github.com/KyleBanks/depth v1.2.1 // indirect
 	github.com/Microsoft/hcsshim v0.9.10 // indirect
 	github.com/RichardKnop/logging v0.0.0-20190827224416-1a693bdd4fae // indirect
@@ -176,7 +175,7 @@ require (
 	github.com/kelseyhightower/envconfig v1.4.0 // indirect
 	github.com/klauspost/compress v1.17.0 // indirect
 	github.com/klauspost/cpuid/v2 v2.2.6 // indirect
-	github.com/leodido/go-urn v1.3.0 // indirect
+	github.com/leodido/go-urn v1.4.0 // indirect
 	github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
 	github.com/magiconair/properties v1.8.7 // indirect
 	github.com/mailru/easyjson v0.7.7 // indirect
@@ -186,10 +185,8 @@ require (
 	github.com/mdlayher/socket v0.4.1 // indirect
 	github.com/microsoft/go-mssqldb v0.17.0 // indirect
 	github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
-	github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
-	github.com/morikuni/aec v1.0.0 // indirect
 	github.com/opencontainers/go-digest v1.0.0 // indirect
 	github.com/opentracing/opentracing-go v1.2.0 // indirect
 	github.com/pelletier/go-toml/v2 v2.1.1 // indirect
@@ -237,7 +234,7 @@ require (
 	golang.org/x/tools v0.16.1 // indirect
 	google.golang.org/appengine v1.6.8 // indirect
 	google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
diff --git a/go.sum b/go.sum
index 56e4a971..eb07ae78 100644
--- a/go.sum
+++ b/go.sum
@@ -49,8 +49,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
 cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
-d7y.io/api/v2 v2.0.95 h1:Zwwh9ZD2Z2i1jD38iJVZWCsrcwhEMNlHaSg34zzAut0=
-d7y.io/api/v2 v2.0.95/go.mod h1:S2boO9+lMAOvLJJIMJeb1kLAitmWdLgOSEExXzknY3s=
+d7y.io/api/v2 v2.0.97 h1:3G4uTrMClTpfiP59bztW7jEG5bhczA4DN88J82qAyag=
+d7y.io/api/v2 v2.0.97/go.mod h1:mvzRC6IMf+L1QEAnFLABDFaLcyYRp4w37cznzAC9ph0=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
@@ -58,8 +58,6 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0/go.mod h1:uGG2W01BaETf0Ozp+Q
 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0/go.mod h1:+6sju8gk8FRmSajX3Oz4G5Gm7P+mbqE9FVaXXFYTkCM=
 github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
 github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
-github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
-github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
 github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
 github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
 github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
@@ -155,8 +153,8 @@ github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN
 github.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
 github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
 github.com/aws/aws-sdk-go v1.37.16/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
-github.com/aws/aws-sdk-go v1.49.17 h1:Cc+7LgPjKeJkF2SdNo1IkpQ5Dfl9HCZEVw9OP3CPuEI=
-github.com/aws/aws-sdk-go v1.49.17/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
+github.com/aws/aws-sdk-go v1.50.25 h1:vhiHtLYybv1Nhx3Kv18BBC6L0aPJHaG9aeEsr92W99c=
+github.com/aws/aws-sdk-go v1.50.25/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
 github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
 github.com/aws/aws-sdk-go-v2 v1.8.1/go.mod h1:xEFuWz+3TYdlPRuo+CqATbeDWIWyaT5uAPwPaWtgse0=
 github.com/aws/aws-sdk-go-v2/service/acmpca v1.6.3/go.mod h1:b8TQvsqtuWslJ4o+eXiHV59P4t20d0EJJ/YVICGJu/o=
@@ -238,8 +236,8 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
-github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101 h1:7To3pQ+pZo0i3dsWEbinPNFs5gPSBOsJtx3wTT94VBY=
-github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ=
+github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM=
 github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
@@ -359,8 +357,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
 github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
-github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
-github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
 github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
 github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
 github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
@@ -397,8 +393,8 @@ github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4Kfc
 github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/docker v1.4.2-0.20200319182547-c7ad2b866182/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
-github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
-github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/docker v25.0.3+incompatible h1:D5fy/lYmY7bvZa0XTZ5/UJPljor41F+vdyJG5luQLfQ=
+github.com/docker/docker v25.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
 github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
 github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
@@ -539,8 +535,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
 github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
 github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
-github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74=
-github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
+github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U=
+github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
 github.com/go-redis/cache/v8 v8.4.3 h1:+RZ0pQM+zOd6h/oWCsOl3+nsCgii9rn26oCYmU87kN8=
 github.com/go-redis/cache/v8 v8.4.3/go.mod h1:5lQPQ63uyBt4aZuRmdvUJOJRRjPxfLtJtlcJ/z8o1jA=
 github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
@@ -966,8 +962,8 @@ github.com/kylelemons/go-gypsy v0.0.0-20160905020020-08cad365cd28/go.mod h1:T/T7
 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
 github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
-github.com/leodido/go-urn v1.3.0 h1:jX8FDLfW4ThVXctBNZ+3cIWnCSnrACDV73r76dy0aQQ=
-github.com/leodido/go-urn v1.3.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
+github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
+github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
 github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@@ -1061,8 +1057,6 @@ github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2J
 github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ=
 github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
 github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
-github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA=
-github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -1075,7 +1069,6 @@ github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJ
 github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
 github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
 github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
-github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
 github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
 github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
 github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
@@ -1822,7 +1815,6 @@ golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -2032,8 +2024,8 @@ google.golang.org/genproto v0.0.0-20210202153253-cf70463f6119/go.mod h1:FWY/as6D
 google.golang.org/genproto v0.0.0-20210207032614-bba0dbe2a9ea/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe h1:USL2DhxfgRchafRvt/wYyyQNzwgL7ZiURcozOE/Pkvo=
 google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro=
-google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac h1:OZkkudMUu9LVQMCoRUbI/1p5VCo9BOrlvkqMvWtqa6s=
-google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:B5xPO//w8qmBDjGReYLpR6UJPnkldGkCSMoH/2vxJeg=
+google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU=
+google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014 h1:FSL3lRCkhaPFxqi0s9o+V4UI2WTzAVOvkgbd4kVV4Wg=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014/go.mod h1:SaPjaZGWb0lPqs6Ittu0spdfrOArqji4ZdeP5IC/9N4=
 google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
@@ -2064,8 +2056,8 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5
 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
-google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0=
-google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
+google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk=
+google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -2127,8 +2119,8 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gorm.io/driver/mysql v1.0.3/go.mod h1:twGxftLBlFgNVNakL7F+P/x9oYqoymG3YYT8cAfI9oI=
 gorm.io/driver/mysql v1.1.2/go.mod h1:4P/X9vSc3WTrhTLZ259cpFd6xKNYiSSdSZngkSBGIMM=
 gorm.io/driver/mysql v1.3.2/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U=
-gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs=
-gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8=
+gorm.io/driver/mysql v1.5.4 h1:igQmHfKcbaTVyAIHNhhB888vvxh8EdQ2uSUT0LPcBso=
+gorm.io/driver/mysql v1.5.4/go.mod h1:9rYxJph/u9SWkWc9yY4XJ1F/+xO0S/ChOmbk3+Z5Tvs=
 gorm.io/driver/postgres v1.2.2/go.mod h1:Ik3tK+a3FMp8ORZl29v4b3M0RsgXsaeMXh9s9eVMXco=
 gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0=
 gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8=
@@ -2147,9 +2139,8 @@ gorm.io/gorm v1.23.0/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
 gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
 gorm.io/gorm v1.23.6/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
 gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
-gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
-gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
-gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
+gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde h1:9DShaph9qhkIYw7QF91I/ynrr4cOO2PZra2PFD7Mfeg=
+gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
 gorm.io/plugin/dbresolver v1.1.0/go.mod h1:tpImigFAEejCALOttyhWqsy4vfa2Uh/vAUVnL5IRF7Y=
 gorm.io/plugin/dbresolver v1.3.0 h1:uFDX3bIuH9Lhj5LY2oyqR/bU6pqWuDgas35NAPF4X3M=
 gorm.io/plugin/dbresolver v1.3.0/go.mod h1:Pr7p5+JFlgDaiM6sOrli5olekJD16YRunMyA2S7ZfKk=
diff --git a/internal/dflog/logcore.go b/internal/dflog/logcore.go
index e03e0fe4..e9371409 100644
--- a/internal/dflog/logcore.go
+++ b/internal/dflog/logcore.go
@@ -25,6 +25,12 @@ import (
 	"gopkg.in/natefinch/lumberjack.v2"
 )
 
+type LogRotateConfig struct {
+	MaxSize    int
+	MaxAge     int
+	MaxBackups int
+}
+
 var (
 	CoreLogFileName       = "core.log"
 	GrpcLogFileName       = "grpc.log"
@@ -37,12 +43,6 @@ var (
 	KeepAliveLogFileName  = "keepalive.log"
 )
 
-const (
-	defaultRotateMaxSize    = 1024
-	defaultRotateMaxBackups = 20
-	defaultRotateMaxAge     = 7
-)
-
 const (
 	encodeTimeFormat = "2006-01-02 15:04:05.000"
 )
@@ -52,12 +52,13 @@ var customCoreLevel atomic.Bool
 var grpcLevel = zap.NewAtomicLevelAt(zapcore.WarnLevel)
 var customGrpcLevel atomic.Bool
 
-func CreateLogger(filePath string, compress bool, stats bool, verbose bool) (*zap.Logger, zap.AtomicLevel, error) {
+func CreateLogger(filePath string, compress bool, stats bool, verbose bool, config LogRotateConfig) (*zap.Logger, zap.AtomicLevel, error) {
+
 	rotateConfig := &lumberjack.Logger{
 		Filename:   filePath,
-		MaxSize:    defaultRotateMaxSize,
-		MaxAge:     defaultRotateMaxAge,
-		MaxBackups: defaultRotateMaxBackups,
+		MaxSize:    config.MaxSize,
+		MaxAge:     config.MaxAge,
+		MaxBackups: config.MaxBackups,
 		LocalTime:  true,
 		Compress:   compress,
 	}
diff --git a/internal/dflog/loginit.go b/internal/dflog/loginit.go
index 7105f7c3..740cb606 100644
--- a/internal/dflog/loginit.go
+++ b/internal/dflog/loginit.go
@@ -33,7 +33,7 @@ type logInitMeta struct {
 	setLoggerFunc        func(log *zap.Logger)
 }
 
-func InitManager(verbose, console bool, dir string) error {
+func InitManager(verbose, console bool, dir string, rotateConfig LogRotateConfig) error {
 	if console {
 		return createConsoleLogger(verbose)
 	}
@@ -62,10 +62,10 @@ func InitManager(verbose, console bool, dir string) error {
 		},
 	}
 
-	return createFileLogger(verbose, meta, logDir)
+	return createFileLogger(verbose, meta, logDir, rotateConfig)
 }
 
-func InitScheduler(verbose, console bool, dir string) error {
+func InitScheduler(verbose, console bool, dir string, rotateConfig LogRotateConfig) error {
 	if console {
 		return createConsoleLogger(verbose)
 	}
@@ -90,10 +90,10 @@ func InitScheduler(verbose, console bool, dir string) error {
 		},
 	}
 
-	return createFileLogger(verbose, meta, logDir)
+	return createFileLogger(verbose, meta, logDir, rotateConfig)
 }
 
-func InitDaemon(verbose, console bool, dir string) error {
+func InitDaemon(verbose, console bool, dir string, rotateConfig LogRotateConfig) error {
 	if console {
 		return createConsoleLogger(verbose)
 	}
@@ -118,10 +118,10 @@ func InitDaemon(verbose, console bool, dir string) error {
 		},
 	}
 
-	return createFileLogger(verbose, meta, logDir)
+	return createFileLogger(verbose, meta, logDir, rotateConfig)
 }
 
-func InitDfget(verbose, console bool, dir string) error {
+func InitDfget(verbose, console bool, dir string, rotateConfig LogRotateConfig) error {
 	if console {
 		return createConsoleLogger(verbose)
 	}
@@ -138,10 +138,10 @@ func InitDfget(verbose, console bool, dir string) error {
 		},
 	}
 
-	return createFileLogger(verbose, meta, logDir)
+	return createFileLogger(verbose, meta, logDir, rotateConfig)
 }
 
-func InitDfcache(console bool, dir string) error {
+func InitDfcache(console bool, dir string, rotateConfig LogRotateConfig) error {
 	logDir := filepath.Join(dir, types.DfcacheName)
 	var meta = []logInitMeta{
 		{
@@ -154,10 +154,10 @@ func InitDfcache(console bool, dir string) error {
 		},
 	}
 
-	return createFileLogger(console, meta, logDir)
+	return createFileLogger(console, meta, logDir, rotateConfig)
 }
 
-func InitTrainer(verbose, console bool, dir string) error {
+func InitTrainer(verbose, console bool, dir string, rotateConfig LogRotateConfig) error {
 	if console {
 		return createConsoleLogger(verbose)
 	}
@@ -174,7 +174,7 @@ func InitTrainer(verbose, console bool, dir string) error {
 		},
 	}
 
-	return createFileLogger(console, meta, logDir)
+	return createFileLogger(console, meta, logDir, rotateConfig)
 }
 
 func createConsoleLogger(verbose bool) error {
@@ -202,13 +202,13 @@ func createConsoleLogger(verbose bool) error {
 	return nil
 }
 
-func createFileLogger(verbose bool, meta []logInitMeta, logDir string) error {
+func createFileLogger(verbose bool, meta []logInitMeta, logDir string, rotateConfig LogRotateConfig) error {
 	levels = nil
 	// create parent dir first
 	_ = os.MkdirAll(logDir, fs.FileMode(0700))
 
 	for _, m := range meta {
-		log, level, err := CreateLogger(path.Join(logDir, m.fileName), false, false, verbose)
+		log, level, err := CreateLogger(path.Join(logDir, m.fileName), false, false, verbose, rotateConfig)
 		if err != nil {
 			return err
 		}
diff --git a/manager/config/config.go b/manager/config/config.go
index ff7480f7..5c5e300d 100644
--- a/manager/config/config.go
+++ b/manager/config/config.go
@@ -78,6 +78,15 @@ type ServerConfig struct {
 	// Server log directory.
 	LogDir string `yaml:"logDir" mapstructure:"logDir"`
 
+	// Maximum size in megabytes of log files before rotation (default: 1024)
+	LogMaxSize int `yaml:"logMaxSize" mapstructure:"logMaxSize"`
+
+	// Maximum number of days to retain old log files (default: 7)
+	LogMaxAge int `yaml:"logMaxAge" mapstructure:"logMaxAge"`
+
+	// Maximum number of old log files to keep (default: 20)
+	LogMaxBackups int `yaml:"logMaxBackups" mapstructure:"logMaxBackups"`
+
 	// Server plugin directory.
 	PluginDir string `yaml:"pluginDir" mapstructure:"pluginDir"`
 
@@ -397,6 +406,9 @@ func New() *Config {
 			REST: RESTConfig{
 				Addr: DefaultRESTAddr,
 			},
+			LogMaxSize:    DefaultLogRotateMaxSize,
+			LogMaxAge:     DefaultLogRotateMaxAge,
+			LogMaxBackups: DefaultLogRotateMaxBackups,
 		},
 		Auth: AuthConfig{
 			JWT: JWTConfig{
diff --git a/manager/config/constants.go b/manager/config/constants.go
index 7b636dc8..75e6ad8e 100644
--- a/manager/config/constants.go
+++ b/manager/config/constants.go
@@ -122,6 +122,17 @@ const (
 	DefaultMetricsAddr = ":8000"
 )
 
+const (
+	// DefaultLogRotateMaxSize is the default maximum size in megabytes of log files before rotation.
+	DefaultLogRotateMaxSize = 1024
+
+	// DefaultLogRotateMaxAge is the default number of days to retain old log files.
+	DefaultLogRotateMaxAge = 7
+
+	// DefaultLogRotateMaxBackups is the default number of old log files to keep.
+	DefaultLogRotateMaxBackups = 20
+)
+
 var (
 	// DefaultCertIPAddresses is default ip addresses of certificate.
 	DefaultCertIPAddresses = []net.IP{ip.IPv4, ip.IPv6}
diff --git a/pkg/log/log.go b/pkg/log/log.go
index 2e9de856..bdb91751 100644
--- a/pkg/log/log.go
+++ b/pkg/log/log.go
@@ -34,7 +34,7 @@ func SetGrpcLevel(level zapcore.Level) {
 }
 
 // SetupDaemon sets daemon log config: path, console
-func SetupDaemon(logDir string, verbose bool, console bool) error {
+func SetupDaemon(logDir string, verbose bool, console bool, rotateConfig logger.LogRotateConfig) error {
 	var options []dfpath.Option
 	if logDir != "" {
 		options = append(options, dfpath.WithLogDir(logDir))
@@ -45,5 +45,5 @@ func SetupDaemon(logDir string, verbose bool, console bool) error {
 		return err
 	}
 
-	return logger.InitDaemon(verbose, console, d.LogDir())
+	return logger.InitDaemon(verbose, console, d.LogDir(), rotateConfig)
 }
diff --git a/scheduler/config/config.go b/scheduler/config/config.go
index 0334cfa1..38358a4e 100644
--- a/scheduler/config/config.go
+++ b/scheduler/config/config.go
@@ -102,6 +102,15 @@ type ServerConfig struct {
 	// Server log directory.
 	LogDir string `yaml:"logDir" mapstructure:"logDir"`
 
+	// Maximum size in megabytes of log files before rotation (default: 1024)
+	LogMaxSize int `yaml:"logMaxSize" mapstructure:"logMaxSize"`
+
+	// Maximum number of days to retain old log files (default: 7)
+	LogMaxAge int `yaml:"logMaxAge" mapstructure:"logMaxAge"`
+
+	// Maximum number of old log files to keep (default: 20)
+	LogMaxBackups int `yaml:"logMaxBackups" mapstructure:"logMaxBackups"`
+
 	// Server plugin directory.
 	PluginDir string `yaml:"pluginDir" mapstructure:"pluginDir"`
 
@@ -378,6 +387,9 @@ func New() *Config {
 			Port:          DefaultServerPort,
 			AdvertisePort: DefaultServerAdvertisePort,
 			Host:          fqdn.FQDNHostname,
+			LogMaxSize:    DefaultLogRotateMaxSize,
+			LogMaxAge:     DefaultLogRotateMaxAge,
+			LogMaxBackups: DefaultLogRotateMaxBackups,
 		},
 		Scheduler: SchedulerConfig{
 			Algorithm:              DefaultSchedulerAlgorithm,
diff --git a/scheduler/config/constants.go b/scheduler/config/constants.go
index 2d3449f6..95b93291 100644
--- a/scheduler/config/constants.go
+++ b/scheduler/config/constants.go
@@ -200,3 +200,14 @@ const (
 	// DefaultTrainerUploadTimeout is the default timeout of uploading dataset to trainer.
 	DefaultTrainerUploadTimeout = 1 * time.Hour
 )
+
+const (
+	// DefaultLogRotateMaxSize is the default maximum size in megabytes of log files before rotation.
+	DefaultLogRotateMaxSize = 1024
+
+	// DefaultLogRotateMaxAge is the default number of days to retain old log files.
+	DefaultLogRotateMaxAge = 7
+
+	// DefaultLogRotateMaxBackups is the default number of old log files to keep.
+	DefaultLogRotateMaxBackups = 20
+)
diff --git a/trainer/config/config.go b/trainer/config/config.go
index 45b8d228..12411544 100644
--- a/trainer/config/config.go
+++ b/trainer/config/config.go
@@ -69,6 +69,15 @@ type ServerConfig struct {
 	// Server log directory.
 	LogDir string `yaml:"logDir" mapstructure:"logDir"`
 
+	// Maximum size in megabytes of log files before rotation (default: 1024)
+	LogMaxSize int `yaml:"logMaxSize" mapstructure:"logMaxSize"`
+
+	// Maximum number of days to retain old log files (default: 7)
+	LogMaxAge int `yaml:"logMaxAge" mapstructure:"logMaxAge"`
+
+	// Maximum number of old log files to keep (default: 20)
+	LogMaxBackups int `yaml:"logMaxBackups" mapstructure:"logMaxBackups"`
+
 	// Server storage data directory.
 	DataDir string `yaml:"dataDir" mapstructure:"dataDir"`
 }
@@ -127,6 +136,9 @@ func New() *Config {
 		Server: ServerConfig{
 			AdvertisePort: DefaultServerAdvertisePort,
 			Port:          DefaultServerPort,
+			LogMaxSize:    DefaultLogRotateMaxSize,
+			LogMaxAge:     DefaultLogRotateMaxAge,
+			LogMaxBackups: DefaultLogRotateMaxBackups,
 		},
 		Metrics: MetricsConfig{
 			Enable: false,
diff --git a/trainer/config/constants.go b/trainer/config/constants.go
index de8e6672..b6c51243 100644
--- a/trainer/config/constants.go
+++ b/trainer/config/constants.go
@@ -51,3 +51,14 @@ var (
 	// DefaultNetworkEnableIPv6 is default value of enableIPv6.
 	DefaultNetworkEnableIPv6 = false
 )
+
+const (
+	// DefaultLogRotateMaxSize is the default maximum size in megabytes of log files before rotation.
+	DefaultLogRotateMaxSize = 1024
+
+	// DefaultLogRotateMaxAge is the default number of days to retain old log files.
+	DefaultLogRotateMaxAge = 7
+
+	// DefaultLogRotateMaxBackups is the default number of old log files to keep.
+	DefaultLogRotateMaxBackups = 20
+)



Test patch:
diff --git a/client/config/peerhost_test.go b/client/config/peerhost_test.go
index 0a1a78ca..3f81971b 100644
--- a/client/config/peerhost_test.go
+++ b/client/config/peerhost_test.go
@@ -247,16 +247,19 @@ func TestPeerHostOption_Load(t *testing.T) {
 		GCInterval: util.Duration{
 			Duration: 60000000000,
 		},
-		Metrics:      ":8000",
-		WorkHome:     "/tmp/dragonfly/dfdaemon/",
-		WorkHomeMode: 0700,
-		CacheDir:     "/var/cache/dragonfly/",
-		CacheDirMode: 0700,
-		LogDir:       "/var/log/dragonfly/",
-		PluginDir:    "/tmp/dragonfly/dfdaemon/plugins/",
-		DataDir:      "/var/lib/dragonfly/",
-		DataDirMode:  0700,
-		KeepStorage:  false,
+		Metrics:       ":8000",
+		WorkHome:      "/tmp/dragonfly/dfdaemon/",
+		WorkHomeMode:  0700,
+		CacheDir:      "/var/cache/dragonfly/",
+		CacheDirMode:  0700,
+		LogDir:        "/var/log/dragonfly/",
+		PluginDir:     "/tmp/dragonfly/dfdaemon/plugins/",
+		DataDir:       "/var/lib/dragonfly/",
+		LogMaxSize:    512,
+		LogMaxAge:     5,
+		LogMaxBackups: 3,
+		DataDirMode:   0700,
+		KeepStorage:   false,
 		Scheduler: SchedulerOption{
 			Manager: ManagerOption{
 				Enable: false,
diff --git a/client/config/testdata/config/daemon.yaml b/client/config/testdata/config/daemon.yaml
index 4537fc02..e54044e1 100644
--- a/client/config/testdata/config/daemon.yaml
+++ b/client/config/testdata/config/daemon.yaml
@@ -11,6 +11,9 @@ workHomeMode: 0700
 cacheDir: /var/cache/dragonfly/
 cacheDirMode: 0700
 logDir: /var/log/dragonfly/
+logMaxSize: 512
+logMaxAge: 5
+logMaxBackups: 3
 pluginDir: /tmp/dragonfly/dfdaemon/plugins/
 dataDir: /var/lib/dragonfly/
 dataDirMode: 0700
diff --git a/manager/config/config_test.go b/manager/config/config_test.go
index 7cbc2178..9dc260e7 100644
--- a/manager/config/config_test.go
+++ b/manager/config/config_test.go
@@ -113,11 +113,14 @@ var (
 func TestConfig_Load(t *testing.T) {
 	config := &Config{
 		Server: ServerConfig{
-			Name:      "foo",
-			WorkHome:  "foo",
-			CacheDir:  "foo",
-			LogDir:    "foo",
-			PluginDir: "foo",
+			Name:          "foo",
+			WorkHome:      "foo",
+			CacheDir:      "foo",
+			LogDir:        "foo",
+			LogMaxSize:    512,
+			LogMaxAge:     5,
+			LogMaxBackups: 3,
+			PluginDir:     "foo",
 			GRPC: GRPCConfig{
 				AdvertiseIP: net.IPv4zero,
 				ListenIP:    net.IPv4zero,
diff --git a/manager/config/testdata/manager.yaml b/manager/config/testdata/manager.yaml
index 7a260b16..884fda29 100644
--- a/manager/config/testdata/manager.yaml
+++ b/manager/config/testdata/manager.yaml
@@ -3,6 +3,9 @@ server:
   workHome: foo
   cacheDir: foo
   logDir: foo
+  logMaxSize: 512
+  logMaxAge: 5
+  logMaxBackups: 3
   pluginDir: foo
   grpc:
     advertiseIP: 0.0.0.0
diff --git a/scheduler/config/config_test.go b/scheduler/config/config_test.go
index a5406b30..92988003 100644
--- a/scheduler/config/config_test.go
+++ b/scheduler/config/config_test.go
@@ -109,6 +109,9 @@ func TestConfig_Load(t *testing.T) {
 			WorkHome:      "foo",
 			CacheDir:      "foo",
 			LogDir:        "foo",
+			LogMaxSize:    512,
+			LogMaxAge:     5,
+			LogMaxBackups: 3,
 			PluginDir:     "foo",
 			DataDir:       "foo",
 		},
diff --git a/scheduler/config/testdata/scheduler.yaml b/scheduler/config/testdata/scheduler.yaml
index 99d8261d..c6a9bc10 100644
--- a/scheduler/config/testdata/scheduler.yaml
+++ b/scheduler/config/testdata/scheduler.yaml
@@ -9,6 +9,9 @@ server:
   logDir: foo
   pluginDir: foo
   dataDir: foo
+  logMaxSize: 512
+  logMaxAge: 5
+  logMaxBackups: 3
 
 scheduler:
   algorithm: default
diff --git a/trainer/config/config_test.go b/trainer/config/config_test.go
index b293fe4c..c274a551 100644
--- a/trainer/config/config_test.go
+++ b/trainer/config/config_test.go
@@ -62,6 +62,9 @@ func TestConfig_Load(t *testing.T) {
 			ListenIP:      net.ParseIP("0.0.0.0"),
 			Port:          9090,
 			LogDir:        "foo",
+			LogMaxSize:    512,
+			LogMaxAge:     5,
+			LogMaxBackups: 3,
 			DataDir:       "foo",
 		},
 		Metrics: MetricsConfig{
diff --git a/trainer/config/testdata/trainer.yaml b/trainer/config/testdata/trainer.yaml
index caf4e0b8..67b1e0f4 100644
--- a/trainer/config/testdata/trainer.yaml
+++ b/trainer/config/testdata/trainer.yaml
@@ -9,6 +9,9 @@ server:
   host: foo
   logDir: foo
   dataDir: foo
+  logMaxSize: 512
+  logMaxAge: 5
+  logMaxBackups: 3
 
 metrics:
   enable: false