mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 03:11:40 +00:00
k8s.io/component-base/logs: allow overriding os.Stdout and os.Stderr
This is useful for tests which need to discard or capture the output.
This commit is contained in:
parent
9b86f457e9
commit
a41424d4c8
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user