diff --git a/cmd/base/options/generic.go b/cmd/base/options/generic.go index ed9b9f6351..01689fc1be 100644 --- a/cmd/base/options/generic.go +++ b/cmd/base/options/generic.go @@ -115,7 +115,7 @@ func (o *GenericOptions) ApplyTo(c *generic.GenericConfiguration) error { errList := make([]error, 0, 1) errList = append(errList, o.qosOptions.ApplyTo(c.QoSConfiguration)) errList = append(errList, o.metricsOptions.ApplyTo(c.MetricsConfiguration)) - errList = append(errList, o.logsOptions.ApplyTo()) + errList = append(errList, o.logsOptions.ApplyTo(c.LogConfiguration)) errList = append(errList, o.authOptions.ApplyTo(c.AuthConfiguration)) c.ClientConnection.QPS = o.QPS diff --git a/cmd/base/options/log.go b/cmd/base/options/log.go index 356cbed78e..7af1101db9 100644 --- a/cmd/base/options/log.go +++ b/cmd/base/options/log.go @@ -19,12 +19,17 @@ package options import ( "github.com/spf13/pflag" + "github.com/kubewharf/katalyst-core/pkg/config/generic" "github.com/kubewharf/katalyst-core/pkg/util/general" ) type LogsOptions struct { LogPackageLevel general.LoggingPKG LogFileMaxSizeInMB uint64 + CustomLogDir string + LogBufferSize int + LogFileMaxAge int + LogFileMaxBackups int } func NewLogsOptions() *LogsOptions { @@ -36,12 +41,21 @@ func NewLogsOptions() *LogsOptions { // AddFlags adds flags to the specified FlagSet. func (o *LogsOptions) AddFlags(fs *pflag.FlagSet) { + fs.StringVar(&o.CustomLogDir, "custom_log_dir", o.CustomLogDir, "directory to store logs for custom logger") fs.Var(&o.LogPackageLevel, "logs-package-level", "the default package level for logging") fs.Uint64Var(&o.LogFileMaxSizeInMB, "log-file-max-size", o.LogFileMaxSizeInMB, "Max size of klog file in MB.") + fs.IntVar(&o.LogBufferSize, "log-buffer-size", o.LogBufferSize, "size of the ring buffer to store async logs") + fs.IntVar(&o.LogFileMaxAge, "log-file-max-age", o.LogFileMaxAge, "max age of klog log file in days") + fs.IntVar(&o.LogFileMaxBackups, "log-file-max-backups", o.LogFileMaxBackups, "max number of klog log file backups") } -func (o *LogsOptions) ApplyTo() error { +func (o *LogsOptions) ApplyTo(c *generic.LogConfiguration) error { general.SetDefaultLoggingPackage(o.LogPackageLevel) general.SetLogFileMaxSize(o.LogFileMaxSizeInMB) + c.CustomLogDir = o.CustomLogDir + c.LogBufferSize = o.LogBufferSize + c.LogFileMaxSize = int(o.LogFileMaxSizeInMB) + c.LogFileMaxAge = o.LogFileMaxAge + c.LogFileMaxBackups = o.LogFileMaxBackups return nil } diff --git a/cmd/katalyst-agent/app/agent.go b/cmd/katalyst-agent/app/agent.go index 2267ee0708..619f7dbe8a 100644 --- a/cmd/katalyst-agent/app/agent.go +++ b/cmd/katalyst-agent/app/agent.go @@ -32,6 +32,7 @@ import ( "github.com/kubewharf/katalyst-core/pkg/consts" "github.com/kubewharf/katalyst-core/pkg/metrics" "github.com/kubewharf/katalyst-core/pkg/util/general" + "github.com/kubewharf/katalyst-core/pkg/util/logging" "github.com/kubewharf/katalyst-core/pkg/util/process" ) @@ -45,7 +46,9 @@ const ( // Run starts common and uniformed agent components here, and starts other // specific components in other separate repos (with common components as // dependencies) -func Run(conf *config.Configuration, clientSet *client.GenericClientSet, genericOptions ...katalystbase.GenericOptions) error { +func Run( + conf *config.Configuration, clientSet *client.GenericClientSet, genericOptions ...katalystbase.GenericOptions, +) error { // Set up signals so that we handle the first shutdown signal gracefully. ctx := process.SetupSignalHandler() @@ -60,6 +63,9 @@ func Run(conf *config.Configuration, clientSet *client.GenericClientSet, generic return err } + customLogger := logging.NewCustomLogger(genericCtx, conf.CustomLogDir, conf.LogFileMaxSize, conf.LogFileMaxAge, conf.LogFileMaxBackups, conf.LogBufferSize) + defer customLogger.Shutdown() + for _, genericOption := range genericOptions { genericOption(genericCtx) } @@ -78,7 +84,8 @@ func Run(conf *config.Configuration, clientSet *client.GenericClientSet, generic } // startAgent is used to initialize and start each component in katalyst-agent -func startAgent(ctx context.Context, genericCtx *agent.GenericContext, +func startAgent( + ctx context.Context, genericCtx *agent.GenericContext, conf *config.Configuration, agents map[string]AgentStarter, ) error { componentMap := make(map[string]agent.Component) diff --git a/go.mod b/go.mod index ad98477791..9b520fddaa 100644 --- a/go.mod +++ b/go.mod @@ -30,6 +30,7 @@ require ( github.com/prometheus/common v0.37.0 github.com/prometheus/procfs v0.10.0 github.com/robfig/cron/v3 v3.0.1 + github.com/rs/zerolog v1.34.0 github.com/safchain/ethtool v0.5.10 github.com/samber/lo v1.39.0 github.com/slok/kubewebhook v0.11.0 @@ -49,6 +50,7 @@ require ( golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 gonum.org/v1/gonum v0.8.2 google.golang.org/grpc v1.57.1 + gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/yaml.v3 v3.0.1 gotest.tools/v3 v3.0.3 k8s.io/api v0.26.1 @@ -83,7 +85,7 @@ require ( github.com/containerd/console v1.0.3 // indirect github.com/containerd/ttrpc v1.2.3-0.20231030150553-baadfd8e7956 // indirect github.com/coreos/go-semver v0.3.0 // indirect - github.com/coreos/go-systemd/v22 v22.3.2 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cyphar/filepath-securejoin v0.2.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/distribution v2.8.1+incompatible // indirect @@ -159,7 +161,6 @@ require ( google.golang.org/protobuf v1.31.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/apiextensions-apiserver v0.24.2 // indirect k8s.io/cloud-provider v0.24.16 // indirect diff --git a/go.sum b/go.sum index ef329d2821..d5b0b08f44 100644 --- a/go.sum +++ b/go.sum @@ -196,8 +196,9 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -601,11 +602,16 @@ github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= @@ -797,6 +803,9 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= +github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= github.com/rubiojr/go-vhd v0.0.0-20200706105327-02e210299021/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -1266,8 +1275,11 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/pkg/config/generic/generic_base.go b/pkg/config/generic/generic_base.go index e301687ac8..a7234458af 100644 --- a/pkg/config/generic/generic_base.go +++ b/pkg/config/generic/generic_base.go @@ -36,6 +36,7 @@ type GenericConfiguration struct { *QoSConfiguration *MetricsConfiguration *AuthConfiguration + *LogConfiguration // ClientConnection specifies the kubeconfig file and client connection // settings for the proxy server to use when communicating with the apiserver. @@ -49,5 +50,6 @@ func NewGenericConfiguration() *GenericConfiguration { QoSConfiguration: NewQoSConfiguration(), MetricsConfiguration: NewMetricsConfiguration(), AuthConfiguration: NewAuthConfiguration(), + LogConfiguration: NewLogConfiguration(), } } diff --git a/pkg/config/generic/log.go b/pkg/config/generic/log.go new file mode 100644 index 0000000000..cd168a770a --- /dev/null +++ b/pkg/config/generic/log.go @@ -0,0 +1,29 @@ +/* +Copyright 2022 The Katalyst Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package generic + +type LogConfiguration struct { + CustomLogDir string + LogFileMaxSize int + LogBufferSize int + LogFileMaxAge int + LogFileMaxBackups int +} + +func NewLogConfiguration() *LogConfiguration { + return &LogConfiguration{} +} diff --git a/pkg/util/logging/logger.go b/pkg/util/logging/logger.go new file mode 100644 index 0000000000..f35ad4e115 --- /dev/null +++ b/pkg/util/logging/logger.go @@ -0,0 +1,122 @@ +/* +Copyright 2022 The Katalyst Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package logging + +import ( + "fmt" + "os" + "path" + "path/filepath" + "time" + + "github.com/rs/zerolog/diode" + "gopkg.in/natefinch/lumberjack.v2" + "k8s.io/klog/v2" + + "github.com/kubewharf/katalyst-core/cmd/katalyst-agent/app/agent" + "github.com/kubewharf/katalyst-core/pkg/metrics" +) + +type SeverityName string + +const ( + InfoSeverity SeverityName = "INFO" + WarningSeverity SeverityName = "WARNING" + ErrorSeverity SeverityName = "ERROR" + FatalSeverity SeverityName = "FATAL" +) + +const ( + metricsNameNumDroppedInfoLogs = "number_of_dropped_info_logs" + metricsNameNumDroppedWarningLogs = "number_of_dropped_warning_logs" + metricsNameNumDroppedErrorLogs = "number_of_dropped_error_logs" + metricsNameNumDroppedFatalLogs = "number_of_dropped_fatal_logs" +) + +var ( + basePath = filepath.Base(os.Args[0]) + defaultInfoLogFileName = fmt.Sprintf("%s.%s.log", basePath, InfoSeverity) + defaultWarningLogFileName = fmt.Sprintf("%s.%s.log", basePath, WarningSeverity) + defaultErrorLogFileName = fmt.Sprintf("%s.%s.log", basePath, ErrorSeverity) + defaultFatalLogFileName = fmt.Sprintf("%s.%s.log", basePath, FatalSeverity) +) + +type logInfo struct { + fileName string + metricsName string +} + +var logInfoMap = map[SeverityName]*logInfo{ + InfoSeverity: {fileName: defaultInfoLogFileName, metricsName: metricsNameNumDroppedInfoLogs}, + WarningSeverity: {fileName: defaultWarningLogFileName, metricsName: metricsNameNumDroppedWarningLogs}, + ErrorSeverity: {fileName: defaultErrorLogFileName, metricsName: metricsNameNumDroppedErrorLogs}, + FatalSeverity: {fileName: defaultFatalLogFileName, metricsName: metricsNameNumDroppedFatalLogs}, +} + +type CustomLogger struct { + diodeWriters []diode.Writer +} + +// NewCustomLogger creates a custom logger that can either be asynchronous or synchronous, depending on configuration. +func NewCustomLogger( + agentCtx *agent.GenericContext, logDir string, maxSizeMB, maxAge, maxBackups, bufferSize int, +) *CustomLogger { + wrappedEmitter := agentCtx.EmitterPool.GetDefaultMetricsEmitter() + + // If logDir is not set, we are still using klog's native logger, so we just return an empty logger without calling SetOutput() + if logDir == "" { + return &CustomLogger{} + } + + customLogger := &CustomLogger{} + for severity, logInfo := range logInfoMap { + filePath := path.Join(logDir, logInfo.fileName) + + // lumberjackLogger is a logger that rotates log files + lumberjackLogger := &lumberjack.Logger{ + Filename: filePath, + MaxSize: maxSizeMB, + MaxAge: maxAge, + MaxBackups: maxBackups, + } + + // Enable async logger if buffer size is more than 0; otherwise, use synchronous lumberjack logger + if bufferSize > 0 { + // diodeWriter is a writer that stores logs in a ring buffer and asynchronously flushes them to disk + diodeWriter := diode.NewWriter(lumberjackLogger, bufferSize, 10*time.Millisecond, func(missed int) { + _ = wrappedEmitter.StoreInt64(logInfo.metricsName, int64(missed), metrics.MetricTypeNameRaw) + }) + // Overrides the default synchronous writer with the diode writer + klog.SetOutputBySeverity(string(severity), diodeWriter) + customLogger.diodeWriters = append(customLogger.diodeWriters, diodeWriter) + klog.Infof("custom async logger is enabled for the severity %s", severity) + } else { + klog.SetOutputBySeverity(string(severity), lumberjackLogger) + klog.Infof("custom sync logger is enabled for the severity %s", severity) + } + } + + return customLogger +} + +func (a *CustomLogger) Shutdown() { + klog.Info("[Shutdown] async writer is shutting down...") + klog.Flush() + for _, writer := range a.diodeWriters { + writer.Close() + } +} diff --git a/pkg/util/logging/logger_test.go b/pkg/util/logging/logger_test.go new file mode 100644 index 0000000000..fd1badf994 --- /dev/null +++ b/pkg/util/logging/logger_test.go @@ -0,0 +1,88 @@ +/* +Copyright 2022 The Katalyst Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package logging + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + katalyst_base "github.com/kubewharf/katalyst-core/cmd/base" + "github.com/kubewharf/katalyst-core/cmd/katalyst-agent/app/agent" + metrics_pool "github.com/kubewharf/katalyst-core/pkg/metrics/metrics-pool" +) + +func TestNewCustomLogger(t *testing.T) { + t.Parallel() + testCases := []struct { + name string + logDir string + maxSizeMB int + maxAge int + maxBackups int + bufferSize int + expectedDiodeWritersCount int + }{ + { + name: "Disabled logger (logDir is empty), logger is nil", + logDir: "", + maxSizeMB: 10, + maxAge: 0, + maxBackups: 10, + bufferSize: 1024, + expectedDiodeWritersCount: 0, + }, + { + name: "Synchronous logger (bufferSize=0)", + logDir: t.TempDir(), + maxSizeMB: 1, + maxAge: 1, + maxBackups: 1, + bufferSize: 0, + expectedDiodeWritersCount: 0, + }, + { + name: "Asynchronous logger", + logDir: t.TempDir(), + maxSizeMB: 1, + maxAge: 1, + maxBackups: 1, + bufferSize: 1024, + expectedDiodeWritersCount: 4, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + agentCtx := &agent.GenericContext{ + GenericContext: &katalyst_base.GenericContext{ + EmitterPool: metrics_pool.DummyMetricsEmitterPool{}, + }, + } + + logger := NewCustomLogger(agentCtx, tc.logDir, tc.maxSizeMB, tc.maxAge, tc.maxBackups, tc.bufferSize) + + assert.NotNil(t, logger) + assert.Len(t, logger.diodeWriters, tc.expectedDiodeWritersCount) + assert.NotPanics(t, func() { + logger.Shutdown() + }) + }) + } +}