diff --git a/staging/src/k8s.io/component-base/logs/api/v1/options.go b/staging/src/k8s.io/component-base/logs/api/v1/options.go index 41acb975d94..9990e31fff2 100644 --- a/staging/src/k8s.io/component-base/logs/api/v1/options.go +++ b/staging/src/k8s.io/component-base/logs/api/v1/options.go @@ -19,7 +19,9 @@ package v1 import ( "flag" "fmt" + "io" "math" + "os" "strings" "time" @@ -63,18 +65,41 @@ func NewLoggingConfiguration() *LoggingConfiguration { // The optional FeatureGate controls logging features. If nil, the default for // these features is used. func ValidateAndApply(c *LoggingConfiguration, featureGate featuregate.FeatureGate) error { - return ValidateAndApplyAsField(c, featureGate, nil) + return validateAndApply(c, nil, featureGate, nil) +} + +// ValidateAndApplyWithOptions is a variant of ValidateAndApply which accepts +// additional options beyond those that can be configured through the API. This +// is meant for testing. +func ValidateAndApplyWithOptions(c *LoggingConfiguration, options *LoggingOptions, featureGate featuregate.FeatureGate) error { + return validateAndApply(c, options, featureGate, nil) +} + +// +k8s:deepcopy-gen=false + +// LoggingOptions can be used with ValidateAndApplyWithOptions to override +// certain global defaults. +type LoggingOptions struct { + // ErrorStream can be used to override the os.Stderr default. + ErrorStream io.Writer + + // InfoStream can be used to override the os.Stdout default. + InfoStream io.Writer } // ValidateAndApplyAsField is a variant of ValidateAndApply that should be used // when the LoggingConfiguration is embedded in some larger configuration // structure. func ValidateAndApplyAsField(c *LoggingConfiguration, featureGate featuregate.FeatureGate, fldPath *field.Path) error { + return validateAndApply(c, nil, featureGate, fldPath) +} + +func validateAndApply(c *LoggingConfiguration, options *LoggingOptions, featureGate featuregate.FeatureGate, fldPath *field.Path) error { errs := Validate(c, featureGate, fldPath) if len(errs) > 0 { return errs.ToAggregate() } - return apply(c, featureGate) + return apply(c, options, featureGate) } // Validate can be used to check for invalid settings without applying them. @@ -157,7 +182,7 @@ func featureEnabled(featureGate featuregate.FeatureGate, feature featuregate.Fea return enabled } -func apply(c *LoggingConfiguration, featureGate featuregate.FeatureGate) error { +func apply(c *LoggingConfiguration, options *LoggingOptions, featureGate featuregate.FeatureGate) error { contextualLoggingEnabled := contextualLoggingDefault if featureGate != nil { contextualLoggingEnabled = featureGate.Enabled(ContextualLogging) @@ -168,7 +193,13 @@ func apply(c *LoggingConfiguration, featureGate featuregate.FeatureGate) error { if format.factory == nil { klog.ClearLogger() } else { - log, control := format.factory.Create(*c) + if options == nil { + options = &LoggingOptions{ + ErrorStream: os.Stderr, + InfoStream: os.Stdout, + } + } + log, control := format.factory.Create(*c, *options) if control.SetVerbosityLevel != nil { setverbositylevel.Mutex.Lock() defer setverbositylevel.Mutex.Unlock() diff --git a/staging/src/k8s.io/component-base/logs/api/v1/registry.go b/staging/src/k8s.io/component-base/logs/api/v1/registry.go index 0f333c0bddf..f8fc1f2cae1 100644 --- a/staging/src/k8s.io/component-base/logs/api/v1/registry.go +++ b/staging/src/k8s.io/component-base/logs/api/v1/registry.go @@ -61,7 +61,7 @@ type RuntimeControl struct { // non-default log format. type LogFormatFactory interface { // Create returns a logger with the requested configuration. - Create(c LoggingConfiguration) (logr.Logger, RuntimeControl) + Create(c LoggingConfiguration, o LoggingOptions) (logr.Logger, RuntimeControl) } // RegisterLogFormat registers support for a new logging format. This must be called diff --git a/staging/src/k8s.io/component-base/logs/json/json.go b/staging/src/k8s.io/component-base/logs/json/json.go index 423c88e654f..20723687e98 100644 --- a/staging/src/k8s.io/component-base/logs/json/json.go +++ b/staging/src/k8s.io/component-base/logs/json/json.go @@ -18,7 +18,6 @@ package json import ( "io" - "os" "sync/atomic" "time" @@ -116,7 +115,7 @@ func (f Factory) Feature() featuregate.Feature { return logsapi.LoggingBetaOptions } -func (f Factory) Create(c logsapi.LoggingConfiguration) (logr.Logger, logsapi.RuntimeControl) { +func (f Factory) Create(c logsapi.LoggingConfiguration, o logsapi.LoggingOptions) (logr.Logger, logsapi.RuntimeControl) { // We intentionally avoid all os.File.Sync calls. Output is unbuffered, // therefore we don't need to flush, and calling the underlying fsync // would just slow down writing. @@ -125,9 +124,9 @@ func (f Factory) Create(c logsapi.LoggingConfiguration) (logr.Logger, logsapi.Ru // written to the output stream before the process terminates, but // doesn't need to worry about data not being written because of a // system crash or powerloss. - stderr := zapcore.Lock(AddNopSync(os.Stderr)) + stderr := zapcore.Lock(AddNopSync(o.ErrorStream)) if c.Options.JSON.SplitStream { - stdout := zapcore.Lock(AddNopSync(os.Stdout)) + stdout := zapcore.Lock(AddNopSync(o.InfoStream)) size := c.Options.JSON.InfoBufferSize.Value() if size > 0 { // Prevent integer overflow. diff --git a/test/integration/logs/benchmark/benchmark_test.go b/test/integration/logs/benchmark/benchmark_test.go index 31c5f60605c..6c071ce38cd 100644 --- a/test/integration/logs/benchmark/benchmark_test.go +++ b/test/integration/logs/benchmark/benchmark_test.go @@ -32,7 +32,6 @@ import ( "testing" "time" - "github.com/go-logr/logr" logsapi "k8s.io/component-base/logs/api/v1" logsjson "k8s.io/component-base/logs/json" "k8s.io/klog/v2" @@ -175,9 +174,6 @@ func benchmarkOutputFormats(b *testing.B, config loadGeneratorConfig, discard bo generateOutput(b, config, nil, out) }) b.Run("JSON", func(b *testing.B) { - c := logsapi.NewLoggingConfiguration() - var logger logr.Logger - var flush func() var out1, out2 *os.File if !discard { var err error @@ -192,46 +188,30 @@ func benchmarkOutputFormats(b *testing.B, config loadGeneratorConfig, discard bo } defer out2.Close() } + o := logsapi.LoggingOptions{} + if discard { + o.ErrorStream = io.Discard + o.InfoStream = io.Discard + } else { + o.ErrorStream = out1 + o.InfoStream = out1 + } + b.Run("single-stream", func(b *testing.B) { - if discard { - l, control := logsjson.NewJSONLogger(c.Verbosity, logsjson.AddNopSync(&output), nil, nil) - logger = l - flush = control.Flush - } else { - stderr := os.Stderr - os.Stderr = out1 - defer func() { - os.Stderr = stderr - }() - l, control := logsjson.Factory{}.Create(*c) - logger = l - flush = control.Flush - } + c := logsapi.NewLoggingConfiguration() + logger, control := logsjson.Factory{}.Create(*c, o) klog.SetLogger(logger) defer klog.ClearLogger() - generateOutput(b, config, flush, out1) + generateOutput(b, config, control.Flush, out1) }) b.Run("split-stream", func(b *testing.B) { - if discard { - l, control := logsjson.NewJSONLogger(c.Verbosity, logsjson.AddNopSync(&output), logsjson.AddNopSync(&output), nil) - logger = l - flush = control.Flush - } else { - stdout, stderr := os.Stdout, os.Stderr - os.Stdout, os.Stderr = out1, out2 - defer func() { - os.Stdout, os.Stderr = stdout, stderr - }() - c := logsapi.NewLoggingConfiguration() - c.Options.JSON.SplitStream = true - l, control := logsjson.Factory{}.Create(*c) - logger = l - flush = control.Flush - } + c := logsapi.NewLoggingConfiguration() + c.Options.JSON.SplitStream = true + logger, control := logsjson.Factory{}.Create(*c, o) klog.SetLogger(logger) defer klog.ClearLogger() - generateOutput(b, config, flush, out1, out2) + generateOutput(b, config, control.Flush, out1, out2) }) }) }