mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +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 (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"math"
|
"math"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -63,18 +65,41 @@ func NewLoggingConfiguration() *LoggingConfiguration {
|
|||||||
// The optional FeatureGate controls logging features. If nil, the default for
|
// The optional FeatureGate controls logging features. If nil, the default for
|
||||||
// these features is used.
|
// these features is used.
|
||||||
func ValidateAndApply(c *LoggingConfiguration, featureGate featuregate.FeatureGate) error {
|
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
|
// ValidateAndApplyAsField is a variant of ValidateAndApply that should be used
|
||||||
// when the LoggingConfiguration is embedded in some larger configuration
|
// when the LoggingConfiguration is embedded in some larger configuration
|
||||||
// structure.
|
// structure.
|
||||||
func ValidateAndApplyAsField(c *LoggingConfiguration, featureGate featuregate.FeatureGate, fldPath *field.Path) error {
|
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)
|
errs := Validate(c, featureGate, fldPath)
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
return errs.ToAggregate()
|
return errs.ToAggregate()
|
||||||
}
|
}
|
||||||
return apply(c, featureGate)
|
return apply(c, options, featureGate)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate can be used to check for invalid settings without applying them.
|
// 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
|
return enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
func apply(c *LoggingConfiguration, featureGate featuregate.FeatureGate) error {
|
func apply(c *LoggingConfiguration, options *LoggingOptions, featureGate featuregate.FeatureGate) error {
|
||||||
contextualLoggingEnabled := contextualLoggingDefault
|
contextualLoggingEnabled := contextualLoggingDefault
|
||||||
if featureGate != nil {
|
if featureGate != nil {
|
||||||
contextualLoggingEnabled = featureGate.Enabled(ContextualLogging)
|
contextualLoggingEnabled = featureGate.Enabled(ContextualLogging)
|
||||||
@ -168,7 +193,13 @@ func apply(c *LoggingConfiguration, featureGate featuregate.FeatureGate) error {
|
|||||||
if format.factory == nil {
|
if format.factory == nil {
|
||||||
klog.ClearLogger()
|
klog.ClearLogger()
|
||||||
} else {
|
} 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 {
|
if control.SetVerbosityLevel != nil {
|
||||||
setverbositylevel.Mutex.Lock()
|
setverbositylevel.Mutex.Lock()
|
||||||
defer setverbositylevel.Mutex.Unlock()
|
defer setverbositylevel.Mutex.Unlock()
|
||||||
|
@ -61,7 +61,7 @@ type RuntimeControl struct {
|
|||||||
// non-default log format.
|
// non-default log format.
|
||||||
type LogFormatFactory interface {
|
type LogFormatFactory interface {
|
||||||
// Create returns a logger with the requested configuration.
|
// 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
|
// RegisterLogFormat registers support for a new logging format. This must be called
|
||||||
|
@ -18,7 +18,6 @@ package json
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -116,7 +115,7 @@ func (f Factory) Feature() featuregate.Feature {
|
|||||||
return logsapi.LoggingBetaOptions
|
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,
|
// We intentionally avoid all os.File.Sync calls. Output is unbuffered,
|
||||||
// therefore we don't need to flush, and calling the underlying fsync
|
// therefore we don't need to flush, and calling the underlying fsync
|
||||||
// would just slow down writing.
|
// 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
|
// written to the output stream before the process terminates, but
|
||||||
// doesn't need to worry about data not being written because of a
|
// doesn't need to worry about data not being written because of a
|
||||||
// system crash or powerloss.
|
// system crash or powerloss.
|
||||||
stderr := zapcore.Lock(AddNopSync(os.Stderr))
|
stderr := zapcore.Lock(AddNopSync(o.ErrorStream))
|
||||||
if c.Options.JSON.SplitStream {
|
if c.Options.JSON.SplitStream {
|
||||||
stdout := zapcore.Lock(AddNopSync(os.Stdout))
|
stdout := zapcore.Lock(AddNopSync(o.InfoStream))
|
||||||
size := c.Options.JSON.InfoBufferSize.Value()
|
size := c.Options.JSON.InfoBufferSize.Value()
|
||||||
if size > 0 {
|
if size > 0 {
|
||||||
// Prevent integer overflow.
|
// Prevent integer overflow.
|
||||||
|
@ -32,7 +32,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-logr/logr"
|
|
||||||
logsapi "k8s.io/component-base/logs/api/v1"
|
logsapi "k8s.io/component-base/logs/api/v1"
|
||||||
logsjson "k8s.io/component-base/logs/json"
|
logsjson "k8s.io/component-base/logs/json"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
@ -175,9 +174,6 @@ func benchmarkOutputFormats(b *testing.B, config loadGeneratorConfig, discard bo
|
|||||||
generateOutput(b, config, nil, out)
|
generateOutput(b, config, nil, out)
|
||||||
})
|
})
|
||||||
b.Run("JSON", func(b *testing.B) {
|
b.Run("JSON", func(b *testing.B) {
|
||||||
c := logsapi.NewLoggingConfiguration()
|
|
||||||
var logger logr.Logger
|
|
||||||
var flush func()
|
|
||||||
var out1, out2 *os.File
|
var out1, out2 *os.File
|
||||||
if !discard {
|
if !discard {
|
||||||
var err error
|
var err error
|
||||||
@ -192,46 +188,30 @@ func benchmarkOutputFormats(b *testing.B, config loadGeneratorConfig, discard bo
|
|||||||
}
|
}
|
||||||
defer out2.Close()
|
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) {
|
b.Run("single-stream", func(b *testing.B) {
|
||||||
if discard {
|
c := logsapi.NewLoggingConfiguration()
|
||||||
l, control := logsjson.NewJSONLogger(c.Verbosity, logsjson.AddNopSync(&output), nil, nil)
|
logger, control := logsjson.Factory{}.Create(*c, o)
|
||||||
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
|
|
||||||
}
|
|
||||||
klog.SetLogger(logger)
|
klog.SetLogger(logger)
|
||||||
defer klog.ClearLogger()
|
defer klog.ClearLogger()
|
||||||
generateOutput(b, config, flush, out1)
|
generateOutput(b, config, control.Flush, out1)
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("split-stream", func(b *testing.B) {
|
b.Run("split-stream", func(b *testing.B) {
|
||||||
if discard {
|
c := logsapi.NewLoggingConfiguration()
|
||||||
l, control := logsjson.NewJSONLogger(c.Verbosity, logsjson.AddNopSync(&output), logsjson.AddNopSync(&output), nil)
|
c.Options.JSON.SplitStream = true
|
||||||
logger = l
|
logger, control := logsjson.Factory{}.Create(*c, o)
|
||||||
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
|
|
||||||
}
|
|
||||||
klog.SetLogger(logger)
|
klog.SetLogger(logger)
|
||||||
defer klog.ClearLogger()
|
defer klog.ClearLogger()
|
||||||
generateOutput(b, config, flush, out1, out2)
|
generateOutput(b, config, control.Flush, out1, out2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user