component-base: move v/vmodule/log-flush-frequency into LoggingConfiguration

These three options are the ones from logs.AddFlags which are not deprecated.
Therefore it makes sense to make them available also via the configuration file
support in the one command which currently supports that (kubelet).

Long-term, all commands should use LoggingConfiguration, either with a
configuration file (as in kubelet) or via flags (kube-scheduler,
kube-apiserver, kube-controller-manager).

Short-term, both approaches have to be supported. As the majority of the
commands only use logs.AddFlags, that function by default continues to register
the flags and only leaves that to Options.AddFlags when explicitly requested.

A drive-by bug fix is done for log flushing: the periodic flushing called
klog.Flush and therefore missed explicit flushing of the newer logr
backend. This bug was never present in any release Kubernetes and therefore the
fix is not submitted in a separate PR.
This commit is contained in:
Patrick Ohly 2021-11-02 12:04:41 +01:00
parent 9af2ece18a
commit 3948cb8d1b
23 changed files with 673 additions and 68 deletions

View File

@ -56,6 +56,7 @@ import (
"k8s.io/client-go/util/keyutil" "k8s.io/client-go/util/keyutil"
cliflag "k8s.io/component-base/cli/flag" cliflag "k8s.io/component-base/cli/flag"
"k8s.io/component-base/cli/globalflag" "k8s.io/component-base/cli/globalflag"
"k8s.io/component-base/logs"
_ "k8s.io/component-base/metrics/prometheus/workqueue" // for workqueue metric registration _ "k8s.io/component-base/metrics/prometheus/workqueue" // for workqueue metric registration
"k8s.io/component-base/term" "k8s.io/component-base/term"
"k8s.io/component-base/version" "k8s.io/component-base/version"
@ -146,7 +147,7 @@ cluster's shared state through which all other components interact.`,
fs := cmd.Flags() fs := cmd.Flags()
namedFlagSets := s.Flags() namedFlagSets := s.Flags()
verflag.AddFlags(namedFlagSets.FlagSet("global")) verflag.AddFlags(namedFlagSets.FlagSet("global"))
globalflag.AddGlobalFlags(namedFlagSets.FlagSet("global"), cmd.Name()) globalflag.AddGlobalFlags(namedFlagSets.FlagSet("global"), cmd.Name(), logs.SkipLoggingConfigurationFlags())
options.AddCustomGlobalFlags(namedFlagSets.FlagSet("generic")) options.AddCustomGlobalFlags(namedFlagSets.FlagSet("generic"))
for _, f := range namedFlagSets.FlagSets { for _, f := range namedFlagSets.FlagSets {
fs.AddFlagSet(f) fs.AddFlagSet(f)

View File

@ -55,6 +55,7 @@ import (
cliflag "k8s.io/component-base/cli/flag" cliflag "k8s.io/component-base/cli/flag"
"k8s.io/component-base/cli/globalflag" "k8s.io/component-base/cli/globalflag"
"k8s.io/component-base/configz" "k8s.io/component-base/configz"
"k8s.io/component-base/logs"
"k8s.io/component-base/term" "k8s.io/component-base/term"
"k8s.io/component-base/version" "k8s.io/component-base/version"
"k8s.io/component-base/version/verflag" "k8s.io/component-base/version/verflag"
@ -160,7 +161,7 @@ controller, and serviceaccounts controller.`,
fs := cmd.Flags() fs := cmd.Flags()
namedFlagSets := s.Flags(KnownControllers(), ControllersDisabledByDefault.List()) namedFlagSets := s.Flags(KnownControllers(), ControllersDisabledByDefault.List())
verflag.AddFlags(namedFlagSets.FlagSet("global")) verflag.AddFlags(namedFlagSets.FlagSet("global"))
globalflag.AddGlobalFlags(namedFlagSets.FlagSet("global"), cmd.Name()) globalflag.AddGlobalFlags(namedFlagSets.FlagSet("global"), cmd.Name(), logs.SkipLoggingConfigurationFlags())
registerLegacyGlobalFlags(namedFlagSets) registerLegacyGlobalFlags(namedFlagSets)
for _, f := range namedFlagSets.FlagSets { for _, f := range namedFlagSets.FlagSets {
fs.AddFlagSet(f) fs.AddFlagSet(f)

View File

@ -91,7 +91,7 @@ for more information about scheduling and the kube-scheduler component.`,
nfs := opts.Flags nfs := opts.Flags
verflag.AddFlags(nfs.FlagSet("global")) verflag.AddFlags(nfs.FlagSet("global"))
globalflag.AddGlobalFlags(nfs.FlagSet("global"), cmd.Name()) globalflag.AddGlobalFlags(nfs.FlagSet("global"), cmd.Name(), logs.SkipLoggingConfigurationFlags())
fs := cmd.Flags() fs := cmd.Flags()
for _, f := range nfs.FlagSets { for _, f := range nfs.FlagSets {
fs.AddFlagSet(f) fs.AddFlagSet(f)

View File

@ -40,7 +40,7 @@ func AddGlobalFlags(fs *pflag.FlagSet) {
addCadvisorFlags(fs) addCadvisorFlags(fs)
addCredentialProviderFlags(fs) addCredentialProviderFlags(fs)
verflag.AddFlags(fs) verflag.AddFlags(fs)
logs.AddFlags(fs) logs.AddFlags(fs, logs.SkipLoggingConfigurationFlags())
} }
// normalize replaces underscores with hyphens // normalize replaces underscores with hyphens

View File

@ -187,6 +187,7 @@ var (
"HairpinMode", "HairpinMode",
"HealthzBindAddress", "HealthzBindAddress",
"HealthzPort", "HealthzPort",
"Logging.FlushFrequency",
"Logging.Format", "Logging.Format",
"Logging.Options.JSON.InfoBufferSize.Quantity.Format", "Logging.Options.JSON.InfoBufferSize.Quantity.Format",
"Logging.Options.JSON.InfoBufferSize.Quantity.d.Dec.scale", "Logging.Options.JSON.InfoBufferSize.Quantity.d.Dec.scale",
@ -197,6 +198,9 @@ var (
"Logging.Options.JSON.InfoBufferSize.Quantity.s", "Logging.Options.JSON.InfoBufferSize.Quantity.s",
"Logging.Options.JSON.SplitStream", "Logging.Options.JSON.SplitStream",
"Logging.Sanitization", "Logging.Sanitization",
"Logging.VModule[*].FilePattern",
"Logging.VModule[*].Verbosity",
"Logging.Verbosity",
"TLSCipherSuites[*]", "TLSCipherSuites[*]",
"TLSMinVersion", "TLSMinVersion",
"IPTablesDropBit", "IPTablesDropBit",

View File

@ -53,10 +53,12 @@ kind: KubeletConfiguration
kubeAPIBurst: 10 kubeAPIBurst: 10
kubeAPIQPS: 5 kubeAPIQPS: 5
logging: logging:
flushFrequency: 5000000000
format: text format: text
options: options:
json: json:
infoBufferSize: "0" infoBufferSize: "0"
verbosity: 0
makeIPTablesUtilChains: true makeIPTablesUtilChains: true
maxOpenFiles: 1000000 maxOpenFiles: 1000000
maxPods: 110 maxPods: 110

View File

@ -53,10 +53,12 @@ kind: KubeletConfiguration
kubeAPIBurst: 10 kubeAPIBurst: 10
kubeAPIQPS: 5 kubeAPIQPS: 5
logging: logging:
flushFrequency: 5000000000
format: text format: text
options: options:
json: json:
infoBufferSize: "0" infoBufferSize: "0"
verbosity: 0
makeIPTablesUtilChains: true makeIPTablesUtilChains: true
maxOpenFiles: 1000000 maxOpenFiles: 1000000
maxPods: 110 maxPods: 110

View File

@ -113,6 +113,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) {
VolumePluginDir: DefaultVolumePluginDir, VolumePluginDir: DefaultVolumePluginDir,
Logging: componentbaseconfigv1alpha1.LoggingConfiguration{ Logging: componentbaseconfigv1alpha1.LoggingConfiguration{
Format: "text", Format: "text",
FlushFrequency: 5 * time.Second,
}, },
EnableSystemLogHandler: utilpointer.BoolPtr(true), EnableSystemLogHandler: utilpointer.BoolPtr(true),
EnableProfilingHandler: utilpointer.BoolPtr(true), EnableProfilingHandler: utilpointer.BoolPtr(true),
@ -232,6 +233,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) {
KernelMemcgNotification: false, KernelMemcgNotification: false,
Logging: componentbaseconfigv1alpha1.LoggingConfiguration{ Logging: componentbaseconfigv1alpha1.LoggingConfiguration{
Format: "", Format: "",
FlushFrequency: 5 * time.Second,
Sanitization: false, Sanitization: false,
}, },
EnableSystemLogHandler: utilpointer.Bool(false), EnableSystemLogHandler: utilpointer.Bool(false),
@ -328,6 +330,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) {
VolumePluginDir: DefaultVolumePluginDir, VolumePluginDir: DefaultVolumePluginDir,
Logging: componentbaseconfigv1alpha1.LoggingConfiguration{ Logging: componentbaseconfigv1alpha1.LoggingConfiguration{
Format: "text", Format: "text",
FlushFrequency: 5 * time.Second,
Sanitization: false, Sanitization: false,
}, },
EnableSystemLogHandler: utilpointer.Bool(false), EnableSystemLogHandler: utilpointer.Bool(false),
@ -469,6 +472,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) {
KernelMemcgNotification: true, KernelMemcgNotification: true,
Logging: componentbaseconfigv1alpha1.LoggingConfiguration{ Logging: componentbaseconfigv1alpha1.LoggingConfiguration{
Format: "json", Format: "json",
FlushFrequency: 5 * time.Second,
Sanitization: true, Sanitization: true,
}, },
EnableSystemLogHandler: utilpointer.Bool(true), EnableSystemLogHandler: utilpointer.Bool(true),
@ -614,6 +618,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) {
KernelMemcgNotification: true, KernelMemcgNotification: true,
Logging: componentbaseconfigv1alpha1.LoggingConfiguration{ Logging: componentbaseconfigv1alpha1.LoggingConfiguration{
Format: "json", Format: "json",
FlushFrequency: 5 * time.Second,
Sanitization: true, Sanitization: true,
}, },
EnableSystemLogHandler: utilpointer.Bool(true), EnableSystemLogHandler: utilpointer.Bool(true),
@ -707,6 +712,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) {
VolumePluginDir: DefaultVolumePluginDir, VolumePluginDir: DefaultVolumePluginDir,
Logging: componentbaseconfigv1alpha1.LoggingConfiguration{ Logging: componentbaseconfigv1alpha1.LoggingConfiguration{
Format: "text", Format: "text",
FlushFrequency: 5 * time.Second,
}, },
EnableSystemLogHandler: utilpointer.BoolPtr(true), EnableSystemLogHandler: utilpointer.BoolPtr(true),
EnableProfilingHandler: utilpointer.BoolPtr(true), EnableProfilingHandler: utilpointer.BoolPtr(true),

View File

@ -27,8 +27,12 @@ import (
// AddGlobalFlags explicitly registers flags that libraries (klog, verflag, etc.) register // AddGlobalFlags explicitly registers flags that libraries (klog, verflag, etc.) register
// against the global flagsets from "flag" and "k8s.io/klog/v2". // against the global flagsets from "flag" and "k8s.io/klog/v2".
// We do this in order to prevent unwanted flags from leaking into the component's flagset. // We do this in order to prevent unwanted flags from leaking into the component's flagset.
func AddGlobalFlags(fs *pflag.FlagSet, name string) { //
logs.AddFlags(fs) // k8s.io/component-base/logs.SkipLoggingConfigurationFlags must be used as
// option when the program also uses a LoggingConfiguration struct for
// configuring logging. Then only flags not covered by that get added.
func AddGlobalFlags(fs *pflag.FlagSet, name string, opts ...logs.Option) {
logs.AddFlags(fs, opts...)
fs.BoolP("help", "h", false, fmt.Sprintf("help for %s", name)) fs.BoolP("help", "h", false, fmt.Sprintf("help for %s", name))
} }

View File

@ -17,6 +17,13 @@ limitations under the License.
package config package config
import ( import (
"fmt"
"strconv"
"strings"
"time"
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
@ -86,6 +93,17 @@ type LoggingConfiguration struct {
// Format Flag specifies the structure of log messages. // Format Flag specifies the structure of log messages.
// default value of format is `text` // default value of format is `text`
Format string Format string
// Maximum number of seconds between log flushes. Ignored if the
// selected logging backend writes log messages without buffering.
FlushFrequency time.Duration
// Verbosity is the threshold that determines which log messages are
// logged. Default is zero which logs only the most important
// messages. Higher values enable additional messages. Error messages
// are always logged.
Verbosity VerbosityLevel
// VModule overrides the verbosity threshold for individual files.
// Only supported for "text" log format.
VModule VModuleConfiguration
// [Experimental] When enabled prevents logging of fields tagged as sensitive (passwords, keys, tokens). // [Experimental] When enabled prevents logging of fields tagged as sensitive (passwords, keys, tokens).
// Runtime log sanitization may introduce significant computation overhead and therefore should not be enabled in production.`) // Runtime log sanitization may introduce significant computation overhead and therefore should not be enabled in production.`)
Sanitization bool Sanitization bool
@ -111,3 +129,86 @@ type JSONOptions struct {
// using split streams. The default is zero, which disables buffering. // using split streams. The default is zero, which disables buffering.
InfoBufferSize resource.QuantityValue InfoBufferSize resource.QuantityValue
} }
// VModuleConfiguration is a collection of individual file names or patterns
// and the corresponding verbosity threshold.
type VModuleConfiguration []VModuleItem
var _ pflag.Value = &VModuleConfiguration{}
// VModuleItem defines verbosity for one or more files which match a certain
// glob pattern.
type VModuleItem struct {
// FilePattern is a base file name (i.e. minus the ".go" suffix and
// directory) or a "glob" pattern for such a name. It must not contain
// comma and equal signs because those are separators for the
// corresponding klog command line argument.
FilePattern string
// Verbosity is the threshold for log messages emitted inside files
// that match the pattern.
Verbosity VerbosityLevel
}
// String returns the -vmodule parameter (comma-separated list of pattern=N).
func (vmodule *VModuleConfiguration) String() string {
var patterns []string
for _, item := range *vmodule {
patterns = append(patterns, fmt.Sprintf("%s=%d", item.FilePattern, item.Verbosity))
}
return strings.Join(patterns, ",")
}
// Set parses the -vmodule parameter (comma-separated list of pattern=N).
func (vmodule *VModuleConfiguration) Set(value string) error {
// This code mirrors https://github.com/kubernetes/klog/blob/9ad246211af1ed84621ee94a26fcce0038b69cd1/klog.go#L287-L313
for _, pat := range strings.Split(value, ",") {
if len(pat) == 0 {
// Empty strings such as from a trailing comma can be ignored.
continue
}
patLev := strings.Split(pat, "=")
if len(patLev) != 2 || len(patLev[0]) == 0 || len(patLev[1]) == 0 {
return fmt.Errorf("%q does not have the pattern=N format", pat)
}
pattern := patLev[0]
// 31 instead of 32 to ensure that it also fits into int32.
v, err := strconv.ParseUint(patLev[1], 10, 31)
if err != nil {
return fmt.Errorf("parsing verbosity in %q: %v", pat, err)
}
*vmodule = append(*vmodule, VModuleItem{FilePattern: pattern, Verbosity: VerbosityLevel(v)})
}
return nil
}
func (vmodule *VModuleConfiguration) Type() string {
return "pattern=N,..."
}
// VerbosityLevel represents a klog or logr verbosity threshold.
type VerbosityLevel uint32
var _ pflag.Value = new(VerbosityLevel)
func (l *VerbosityLevel) String() string {
return strconv.FormatInt(int64(*l), 10)
}
func (l *VerbosityLevel) Get() interface{} {
return *l
}
func (l *VerbosityLevel) Set(value string) error {
// Limited to int32 for compatibility with klog.
v, err := strconv.ParseUint(value, 10, 31)
if err != nil {
return err
}
*l = VerbosityLevel(v)
return nil
}
func (l *VerbosityLevel) Type() string {
return "Level"
}

View File

@ -0,0 +1,119 @@
/*
Copyright 2021 The Kubernetes 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 config
import (
"fmt"
"math"
"testing"
"github.com/stretchr/testify/assert"
)
func TestVModule(t *testing.T) {
testcases := []struct {
arg string
expectError string
expectValue VModuleConfiguration
expectParam string
}{
{
arg: "gopher*=1",
expectValue: VModuleConfiguration{
{
FilePattern: "gopher*",
Verbosity: 1,
},
},
},
{
arg: "foo=1,bar=2",
expectValue: VModuleConfiguration{
{
FilePattern: "foo",
Verbosity: 1,
},
{
FilePattern: "bar",
Verbosity: 2,
},
},
},
{
arg: "foo=1,bar=2,",
expectValue: VModuleConfiguration{
{
FilePattern: "foo",
Verbosity: 1,
},
{
FilePattern: "bar",
Verbosity: 2,
},
},
expectParam: "foo=1,bar=2",
},
{
arg: "gopher*",
expectError: `"gopher*" does not have the pattern=N format`,
},
{
arg: "=1",
expectError: `"=1" does not have the pattern=N format`,
},
{
arg: "foo=-1",
expectError: `parsing verbosity in "foo=-1": strconv.ParseUint: parsing "-1": invalid syntax`,
},
{
arg: fmt.Sprintf("validint32=%d", math.MaxInt32),
expectValue: VModuleConfiguration{
{
FilePattern: "validint32",
Verbosity: math.MaxInt32,
},
},
},
{
arg: fmt.Sprintf("invalidint32=%d", math.MaxInt32+1),
expectError: `parsing verbosity in "invalidint32=2147483648": strconv.ParseUint: parsing "2147483648": value out of range`,
},
}
for _, test := range testcases {
t.Run(test.arg, func(t *testing.T) {
var actual VModuleConfiguration
err := actual.Set(test.arg)
if test.expectError != "" {
if err == nil {
t.Fatal("parsing should have failed")
}
assert.Equal(t, test.expectError, err.Error(), "parse error")
} else {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
param := actual.String()
expectParam := test.expectParam
if expectParam == "" {
expectParam = test.arg
}
assert.Equal(t, expectParam, param, "encoded parameter value not identical")
}
})
}
}

View File

@ -122,4 +122,7 @@ func RecommendedLoggingConfiguration(obj *LoggingConfiguration) {
// by reflect.DeepEqual in some tests. // by reflect.DeepEqual in some tests.
_ = obj.Options.JSON.InfoBufferSize.String() _ = obj.Options.JSON.InfoBufferSize.String()
} }
if obj.FlushFrequency == 0 {
obj.FlushFrequency = 5 * time.Second
}
} }

View File

@ -17,6 +17,8 @@ limitations under the License.
package v1alpha1 package v1alpha1
import ( import (
"time"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
@ -88,6 +90,17 @@ type LoggingConfiguration struct {
// Format Flag specifies the structure of log messages. // Format Flag specifies the structure of log messages.
// default value of format is `text` // default value of format is `text`
Format string `json:"format,omitempty"` Format string `json:"format,omitempty"`
// Maximum number of seconds between log flushes. Ignored if the
// selected logging backend writes log messages without buffering.
FlushFrequency time.Duration `json:"flushFrequency"`
// Verbosity is the threshold that determines which log messages are
// logged. Default is zero which logs only the most important
// messages. Higher values enable additional messages. Error messages
// are always logged.
Verbosity uint32 `json:"verbosity"`
// VModule overrides the verbosity threshold for individual files.
// Only supported for "text" log format.
VModule VModuleConfiguration `json:"vmodule,omitempty"`
// [Experimental] When enabled prevents logging of fields tagged as sensitive (passwords, keys, tokens). // [Experimental] When enabled prevents logging of fields tagged as sensitive (passwords, keys, tokens).
// Runtime log sanitization may introduce significant computation overhead and therefore should not be enabled in production.`) // Runtime log sanitization may introduce significant computation overhead and therefore should not be enabled in production.`)
Sanitization bool `json:"sanitization,omitempty"` Sanitization bool `json:"sanitization,omitempty"`
@ -113,3 +126,20 @@ type JSONOptions struct {
// using split streams. The default is zero, which disables buffering. // using split streams. The default is zero, which disables buffering.
InfoBufferSize resource.QuantityValue `json:"infoBufferSize,omitempty"` InfoBufferSize resource.QuantityValue `json:"infoBufferSize,omitempty"`
} }
// VModuleConfiguration is a collection of individual file names or patterns
// and the corresponding verbosity threshold.
type VModuleConfiguration []VModuleItem
// VModuleItem defines verbosity for one or more files which match a certain
// glob pattern.
type VModuleItem struct {
// FilePattern is a base file name (i.e. minus the ".go" suffix and
// directory) or a "glob" pattern for such a name. It must not contain
// comma and equal signs because those are separators for the
// corresponding klog command line argument.
FilePattern string `json:"filePattern"`
// Verbosity is the threshold for log messages emitted inside files
// that match the pattern.
Verbosity uint32 `json:"verbosity"`
}

View File

@ -22,6 +22,9 @@ limitations under the License.
package v1alpha1 package v1alpha1
import ( import (
time "time"
unsafe "unsafe"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
conversion "k8s.io/apimachinery/pkg/conversion" conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime" runtime "k8s.io/apimachinery/pkg/runtime"
@ -55,6 +58,16 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil { }); err != nil {
return err return err
} }
if err := s.AddGeneratedConversionFunc((*VModuleItem)(nil), (*config.VModuleItem)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_VModuleItem_To_config_VModuleItem(a.(*VModuleItem), b.(*config.VModuleItem), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*config.VModuleItem)(nil), (*VModuleItem)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_VModuleItem_To_v1alpha1_VModuleItem(a.(*config.VModuleItem), b.(*VModuleItem), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*config.ClientConnectionConfiguration)(nil), (*ClientConnectionConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { if err := s.AddConversionFunc((*config.ClientConnectionConfiguration)(nil), (*ClientConnectionConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_ClientConnectionConfiguration_To_v1alpha1_ClientConnectionConfiguration(a.(*config.ClientConnectionConfiguration), b.(*ClientConnectionConfiguration), scope) return Convert_config_ClientConnectionConfiguration_To_v1alpha1_ClientConnectionConfiguration(a.(*config.ClientConnectionConfiguration), b.(*ClientConnectionConfiguration), scope)
}); err != nil { }); err != nil {
@ -210,6 +223,9 @@ func autoConvert_config_LeaderElectionConfiguration_To_v1alpha1_LeaderElectionCo
func autoConvert_v1alpha1_LoggingConfiguration_To_config_LoggingConfiguration(in *LoggingConfiguration, out *config.LoggingConfiguration, s conversion.Scope) error { func autoConvert_v1alpha1_LoggingConfiguration_To_config_LoggingConfiguration(in *LoggingConfiguration, out *config.LoggingConfiguration, s conversion.Scope) error {
out.Format = in.Format out.Format = in.Format
out.FlushFrequency = time.Duration(in.FlushFrequency)
out.Verbosity = config.VerbosityLevel(in.Verbosity)
out.VModule = *(*config.VModuleConfiguration)(unsafe.Pointer(&in.VModule))
out.Sanitization = in.Sanitization out.Sanitization = in.Sanitization
if err := Convert_v1alpha1_FormatOptions_To_config_FormatOptions(&in.Options, &out.Options, s); err != nil { if err := Convert_v1alpha1_FormatOptions_To_config_FormatOptions(&in.Options, &out.Options, s); err != nil {
return err return err
@ -219,9 +235,34 @@ func autoConvert_v1alpha1_LoggingConfiguration_To_config_LoggingConfiguration(in
func autoConvert_config_LoggingConfiguration_To_v1alpha1_LoggingConfiguration(in *config.LoggingConfiguration, out *LoggingConfiguration, s conversion.Scope) error { func autoConvert_config_LoggingConfiguration_To_v1alpha1_LoggingConfiguration(in *config.LoggingConfiguration, out *LoggingConfiguration, s conversion.Scope) error {
out.Format = in.Format out.Format = in.Format
out.FlushFrequency = time.Duration(in.FlushFrequency)
out.Verbosity = uint32(in.Verbosity)
out.VModule = *(*VModuleConfiguration)(unsafe.Pointer(&in.VModule))
out.Sanitization = in.Sanitization out.Sanitization = in.Sanitization
if err := Convert_config_FormatOptions_To_v1alpha1_FormatOptions(&in.Options, &out.Options, s); err != nil { if err := Convert_config_FormatOptions_To_v1alpha1_FormatOptions(&in.Options, &out.Options, s); err != nil {
return err return err
} }
return nil return nil
} }
func autoConvert_v1alpha1_VModuleItem_To_config_VModuleItem(in *VModuleItem, out *config.VModuleItem, s conversion.Scope) error {
out.FilePattern = in.FilePattern
out.Verbosity = config.VerbosityLevel(in.Verbosity)
return nil
}
// Convert_v1alpha1_VModuleItem_To_config_VModuleItem is an autogenerated conversion function.
func Convert_v1alpha1_VModuleItem_To_config_VModuleItem(in *VModuleItem, out *config.VModuleItem, s conversion.Scope) error {
return autoConvert_v1alpha1_VModuleItem_To_config_VModuleItem(in, out, s)
}
func autoConvert_config_VModuleItem_To_v1alpha1_VModuleItem(in *config.VModuleItem, out *VModuleItem, s conversion.Scope) error {
out.FilePattern = in.FilePattern
out.Verbosity = uint32(in.Verbosity)
return nil
}
// Convert_config_VModuleItem_To_v1alpha1_VModuleItem is an autogenerated conversion function.
func Convert_config_VModuleItem_To_v1alpha1_VModuleItem(in *config.VModuleItem, out *VModuleItem, s conversion.Scope) error {
return autoConvert_config_VModuleItem_To_v1alpha1_VModuleItem(in, out, s)
}

View File

@ -124,6 +124,11 @@ func (in *LeaderElectionConfiguration) DeepCopy() *LeaderElectionConfiguration {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LoggingConfiguration) DeepCopyInto(out *LoggingConfiguration) { func (in *LoggingConfiguration) DeepCopyInto(out *LoggingConfiguration) {
*out = *in *out = *in
if in.VModule != nil {
in, out := &in.VModule, &out.VModule
*out = make(VModuleConfiguration, len(*in))
copy(*out, *in)
}
in.Options.DeepCopyInto(&out.Options) in.Options.DeepCopyInto(&out.Options)
return return
} }
@ -137,3 +142,39 @@ func (in *LoggingConfiguration) DeepCopy() *LoggingConfiguration {
in.DeepCopyInto(out) in.DeepCopyInto(out)
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in VModuleConfiguration) DeepCopyInto(out *VModuleConfiguration) {
{
in := &in
*out = make(VModuleConfiguration, len(*in))
copy(*out, *in)
return
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VModuleConfiguration.
func (in VModuleConfiguration) DeepCopy() VModuleConfiguration {
if in == nil {
return nil
}
out := new(VModuleConfiguration)
in.DeepCopyInto(out)
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VModuleItem) DeepCopyInto(out *VModuleItem) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VModuleItem.
func (in *VModuleItem) DeepCopy() *VModuleItem {
if in == nil {
return nil
}
out := new(VModuleItem)
in.DeepCopyInto(out)
return out
}

View File

@ -109,6 +109,11 @@ func (in *LeaderElectionConfiguration) DeepCopy() *LeaderElectionConfiguration {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LoggingConfiguration) DeepCopyInto(out *LoggingConfiguration) { func (in *LoggingConfiguration) DeepCopyInto(out *LoggingConfiguration) {
*out = *in *out = *in
if in.VModule != nil {
in, out := &in.VModule, &out.VModule
*out = make(VModuleConfiguration, len(*in))
copy(*out, *in)
}
in.Options.DeepCopyInto(&out.Options) in.Options.DeepCopyInto(&out.Options)
return return
} }
@ -122,3 +127,39 @@ func (in *LoggingConfiguration) DeepCopy() *LoggingConfiguration {
in.DeepCopyInto(out) in.DeepCopyInto(out)
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in VModuleConfiguration) DeepCopyInto(out *VModuleConfiguration) {
{
in := &in
*out = make(VModuleConfiguration, len(*in))
copy(*out, *in)
return
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VModuleConfiguration.
func (in VModuleConfiguration) DeepCopy() VModuleConfiguration {
if in == nil {
return nil
}
out := new(VModuleConfiguration)
in.DeepCopyInto(out)
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VModuleItem) DeepCopyInto(out *VModuleItem) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VModuleItem.
func (in *VModuleItem) DeepCopy() *VModuleItem {
if in == nil {
return nil
}
out := new(VModuleItem)
in.DeepCopyInto(out)
return out
}

View File

@ -52,10 +52,12 @@ func init() {
// List of logs (k8s.io/klog + k8s.io/component-base/logs) flags supported by all logging formats // List of logs (k8s.io/klog + k8s.io/component-base/logs) flags supported by all logging formats
var supportedLogsFlags = map[string]struct{}{ var supportedLogsFlags = map[string]struct{}{
"v": {}, "v": {},
// TODO: support vmodule after 1.19 Alpha
} }
// BindLoggingFlags binds the Options struct fields to a flagset // BindLoggingFlags binds the Options struct fields to a flagset.
//
// Programs using LoggingConfiguration must use SkipLoggingConfigurationFlags
// when calling AddFlags to avoid the duplicate registration of flags.
func BindLoggingFlags(c *config.LoggingConfiguration, fs *pflag.FlagSet) { func BindLoggingFlags(c *config.LoggingConfiguration, fs *pflag.FlagSet) {
// The help text is generated assuming that flags will eventually use // The help text is generated assuming that flags will eventually use
// hyphens, even if currently no normalization function is set for the // hyphens, even if currently no normalization function is set for the
@ -65,6 +67,10 @@ func BindLoggingFlags(c *config.LoggingConfiguration, fs *pflag.FlagSet) {
fs.StringVar(&c.Format, "logging-format", c.Format, fmt.Sprintf("Sets the log format. Permitted formats: %s.\nNon-default formats don't honor these flags: %s.\nNon-default choices are currently alpha and subject to change without warning.", formats, unsupportedFlags)) fs.StringVar(&c.Format, "logging-format", c.Format, fmt.Sprintf("Sets the log format. Permitted formats: %s.\nNon-default formats don't honor these flags: %s.\nNon-default choices are currently alpha and subject to change without warning.", formats, unsupportedFlags))
// No new log formats should be added after generation is of flag options // No new log formats should be added after generation is of flag options
registry.LogRegistry.Freeze() registry.LogRegistry.Freeze()
fs.DurationVar(&c.FlushFrequency, logFlushFreqFlagName, logFlushFreq, "Maximum number of seconds between log flushes")
fs.VarP(&c.Verbosity, "v", "v", "number for the log level verbosity")
fs.Var(&c.VModule, "vmodule", "comma-separated list of pattern=N settings for file-filtered logging (only works for text log format)")
fs.BoolVar(&c.Sanitization, "experimental-logging-sanitization", c.Sanitization, `[Experimental] When enabled prevents logging of fields tagged as sensitive (passwords, keys, tokens). fs.BoolVar(&c.Sanitization, "experimental-logging-sanitization", c.Sanitization, `[Experimental] When enabled prevents logging of fields tagged as sensitive (passwords, keys, tokens).
Runtime log sanitization may introduce significant computation overhead and therefore should not be enabled in production.`) Runtime log sanitization may introduce significant computation overhead and therefore should not be enabled in production.`)

View File

@ -23,9 +23,7 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/component-base/config"
"k8s.io/component-base/logs" "k8s.io/component-base/logs"
) )
@ -43,14 +41,7 @@ func TestJSONFlag(t *testing.T) {
} }
func TestJSONFormatRegister(t *testing.T) { func TestJSONFormatRegister(t *testing.T) {
defaultOptions := config.FormatOptions{ newOptions := logs.NewOptions()
JSON: config.JSONOptions{
InfoBufferSize: resource.QuantityValue{
Quantity: *resource.NewQuantity(0, resource.DecimalSI),
},
},
}
_ = defaultOptions.JSON.InfoBufferSize.String()
testcases := []struct { testcases := []struct {
name string name string
args []string args []string
@ -60,22 +51,20 @@ func TestJSONFormatRegister(t *testing.T) {
{ {
name: "JSON log format", name: "JSON log format",
args: []string{"--logging-format=json"}, args: []string{"--logging-format=json"},
want: &logs.Options{ want: func() *logs.Options {
Config: config.LoggingConfiguration{ c := newOptions.Config.DeepCopy()
Format: logs.JSONLogFormat, c.Format = logs.JSONLogFormat
Options: defaultOptions, return &logs.Options{*c}
}, }(),
},
}, },
{ {
name: "Unsupported log format", name: "Unsupported log format",
args: []string{"--logging-format=test"}, args: []string{"--logging-format=test"},
want: &logs.Options{ want: func() *logs.Options {
Config: config.LoggingConfiguration{ c := newOptions.Config.DeepCopy()
Format: "test", c.Format = "test"
Options: defaultOptions, return &logs.Options{*c}
}, }(),
},
errs: field.ErrorList{&field.Error{ errs: field.ErrorList{&field.Error{
Type: "FieldValueInvalid", Type: "FieldValueInvalid",
Field: "format", Field: "format",

View File

@ -41,8 +41,12 @@ const deprecated = "will be removed in a future release, see https://github.com/
var ( var (
packageFlags = flag.NewFlagSet("logging", flag.ContinueOnError) packageFlags = flag.NewFlagSet("logging", flag.ContinueOnError)
logFlushFreq time.Duration
logrFlush func() logrFlush func()
// Periodic flushing gets configured either via the global flag
// in this file or via LoggingConfiguration.
logFlushFreq time.Duration
logFlushFreqAdded bool
) )
func init() { func init() {
@ -50,6 +54,21 @@ func init() {
packageFlags.DurationVar(&logFlushFreq, logFlushFreqFlagName, 5*time.Second, "Maximum number of seconds between log flushes") packageFlags.DurationVar(&logFlushFreq, logFlushFreqFlagName, 5*time.Second, "Maximum number of seconds between log flushes")
} }
type addFlagsOptions struct {
skipLoggingConfigurationFlags bool
}
type Option func(*addFlagsOptions)
// SkipLoggingConfigurationFlags must be used as option for AddFlags when
// the program also uses a LoggingConfiguration struct for configuring
// logging. Then only flags not covered by that get added.
func SkipLoggingConfigurationFlags() Option {
return func(o *addFlagsOptions) {
o.skipLoggingConfigurationFlags = true
}
}
// AddFlags registers this package's flags on arbitrary FlagSets. This includes // AddFlags registers this package's flags on arbitrary FlagSets. This includes
// the klog flags, with the original underscore as separator between. If // the klog flags, with the original underscore as separator between. If
// commands want hyphens as separators, they can set // commands want hyphens as separators, they can set
@ -57,22 +76,39 @@ func init() {
// function on the flag set before calling AddFlags. // function on the flag set before calling AddFlags.
// //
// May be called more than once. // May be called more than once.
func AddFlags(fs *pflag.FlagSet) { func AddFlags(fs *pflag.FlagSet, opts ...Option) {
// Determine whether the flags are already present by looking up one // Determine whether the flags are already present by looking up one
// which always should exist. // which always should exist.
if f := fs.Lookup("v"); f != nil { if fs.Lookup("logtostderr") != nil {
return return
} }
o := addFlagsOptions{}
for _, opt := range opts {
opt(&o)
}
// Add flags with pflag deprecation remark for some klog flags. // Add flags with pflag deprecation remark for some klog flags.
packageFlags.VisitAll(func(f *flag.Flag) { packageFlags.VisitAll(func(f *flag.Flag) {
pf := pflag.PFlagFromGoFlag(f) pf := pflag.PFlagFromGoFlag(f)
switch f.Name { switch f.Name {
case "v", logFlushFreqFlagName: case "v":
// unchanged // unchanged, potentially skip it
if o.skipLoggingConfigurationFlags {
return
}
case logFlushFreqFlagName:
// unchanged, potentially skip it
if o.skipLoggingConfigurationFlags {
return
}
logFlushFreqAdded = true
case "vmodule": case "vmodule":
// TODO: see above // TODO: see above
// pf.Usage += vmoduleUsage // pf.Usage += vmoduleUsage
if o.skipLoggingConfigurationFlags {
return
}
default: default:
// deprecated, but not hidden // deprecated, but not hidden
pf.Deprecated = deprecated pf.Deprecated = deprecated
@ -87,17 +123,34 @@ func AddFlags(fs *pflag.FlagSet) {
// in flag.CommandLine) and commands that for historic reasons use Go // in flag.CommandLine) and commands that for historic reasons use Go
// flag.Parse and cannot change to pflag because it would break their command // flag.Parse and cannot change to pflag because it would break their command
// line interface. // line interface.
func AddGoFlags(fs *flag.FlagSet) { func AddGoFlags(fs *flag.FlagSet, opts ...Option) {
o := addFlagsOptions{}
for _, opt := range opts {
opt(&o)
}
// Add flags with deprecation remark added to the usage text of // Add flags with deprecation remark added to the usage text of
// some klog flags. // some klog flags.
packageFlags.VisitAll(func(f *flag.Flag) { packageFlags.VisitAll(func(f *flag.Flag) {
usage := f.Usage usage := f.Usage
switch f.Name { switch f.Name {
case "v", logFlushFreqFlagName: case "v":
// unchanged // unchanged
if o.skipLoggingConfigurationFlags {
return
}
case logFlushFreqFlagName:
// unchanged
if o.skipLoggingConfigurationFlags {
return
}
logFlushFreqAdded = true
case "vmodule": case "vmodule":
// TODO: see above // TODO: see above
// usage += vmoduleUsage // usage += vmoduleUsage
if o.skipLoggingConfigurationFlags {
return
}
default: default:
usage += " (DEPRECATED: " + deprecated + ")" usage += " (DEPRECATED: " + deprecated + ")"
} }
@ -120,8 +173,11 @@ func (writer KlogWriter) Write(data []byte) (n int, err error) {
func InitLogs() { func InitLogs() {
log.SetOutput(KlogWriter{}) log.SetOutput(KlogWriter{})
log.SetFlags(0) log.SetFlags(0)
// The default klog flush interval is 5 seconds. if logFlushFreqAdded {
go wait.Forever(klog.Flush, logFlushFreq) // The flag from this file was activated, so use it now.
// Otherwise LoggingConfiguration.Apply will do this.
go wait.Forever(FlushLogs, logFlushFreq)
}
} }
// FlushLogs flushes logs immediately. This should be called at the end of // FlushLogs flushes logs immediately. This should be called at the end of

View File

@ -17,14 +17,16 @@ limitations under the License.
package logs package logs
import ( import (
"fmt"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"k8s.io/klog/v2" "k8s.io/apimachinery/pkg/util/wait"
"k8s.io/component-base/config" "k8s.io/component-base/config"
"k8s.io/component-base/config/v1alpha1" "k8s.io/component-base/config/v1alpha1"
"k8s.io/component-base/logs/registry" "k8s.io/component-base/logs/registry"
"k8s.io/component-base/logs/sanitization" "k8s.io/component-base/logs/sanitization"
"k8s.io/klog/v2"
) )
// Options has klog format parameters // Options has klog format parameters
@ -51,7 +53,10 @@ func (o *Options) Validate() []error {
return nil return nil
} }
// AddFlags add logging-format flag // AddFlags add logging-format flag.
//
// Programs using LoggingConfiguration must use SkipLoggingConfigurationFlags
// when calling AddFlags to avoid the duplicate registration of flags.
func (o *Options) AddFlags(fs *pflag.FlagSet) { func (o *Options) AddFlags(fs *pflag.FlagSet) {
BindLoggingFlags(&o.Config, fs) BindLoggingFlags(&o.Config, fs)
} }
@ -70,4 +75,11 @@ func (o *Options) Apply() {
if o.Config.Sanitization { if o.Config.Sanitization {
klog.SetLogFilter(&sanitization.SanitizingFilter{}) klog.SetLogFilter(&sanitization.SanitizingFilter{})
} }
if err := loggingFlags.Lookup("v").Value.Set(o.Config.Verbosity.String()); err != nil {
panic(fmt.Errorf("internal error while setting klog verbosity: %v", err))
}
if err := loggingFlags.Lookup("vmodule").Value.Set(o.Config.VModule.String()); err != nil {
panic(fmt.Errorf("internal error while setting klog vmodule: %v", err))
}
go wait.Forever(FlushLogs, o.Config.FlushFrequency)
} }

View File

@ -24,7 +24,6 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/component-base/config"
) )
func TestFlags(t *testing.T) { func TestFlags(t *testing.T) {
@ -36,9 +35,12 @@ func TestFlags(t *testing.T) {
fs.PrintDefaults() fs.PrintDefaults()
want := ` --experimental-logging-sanitization [Experimental] When enabled prevents logging of fields tagged as sensitive (passwords, keys, tokens). want := ` --experimental-logging-sanitization [Experimental] When enabled prevents logging of fields tagged as sensitive (passwords, keys, tokens).
Runtime log sanitization may introduce significant computation overhead and therefore should not be enabled in production. Runtime log sanitization may introduce significant computation overhead and therefore should not be enabled in production.
--log-flush-frequency duration Maximum number of seconds between log flushes (default 5s)
--logging-format string Sets the log format. Permitted formats: "text". --logging-format string Sets the log format. Permitted formats: "text".
Non-default formats don't honor these flags: --add-dir-header, --alsologtostderr, --log-backtrace-at, --log-dir, --log-file, --log-file-max-size, --logtostderr, --one-output, --skip-headers, --skip-log-headers, --stderrthreshold, --vmodule. Non-default formats don't honor these flags: --add-dir-header, --alsologtostderr, --log-backtrace-at, --log-dir, --log-file, --log-file-max-size, --logtostderr, --one-output, --skip-headers, --skip-log-headers, --stderrthreshold, --vmodule.
Non-default choices are currently alpha and subject to change without warning. (default "text") Non-default choices are currently alpha and subject to change without warning. (default "text")
-v, --v Level number for the log level verbosity
--vmodule pattern=N,... comma-separated list of pattern=N settings for file-filtered logging (only works for text log format)
` `
if !assert.Equal(t, want, output.String()) { if !assert.Equal(t, want, output.String()) {
t.Errorf("Wrong list of flags. expect %q, got %q", want, output.String()) t.Errorf("Wrong list of flags. expect %q, got %q", want, output.String())
@ -46,6 +48,7 @@ func TestFlags(t *testing.T) {
} }
func TestOptions(t *testing.T) { func TestOptions(t *testing.T) {
newOptions := NewOptions()
testcases := []struct { testcases := []struct {
name string name string
args []string args []string
@ -54,33 +57,30 @@ func TestOptions(t *testing.T) {
}{ }{
{ {
name: "Default log format", name: "Default log format",
want: NewOptions(), want: newOptions,
}, },
{ {
name: "Text log format", name: "Text log format",
args: []string{"--logging-format=text"}, args: []string{"--logging-format=text"},
want: NewOptions(), want: newOptions,
}, },
{ {
name: "log sanitization", name: "log sanitization",
args: []string{"--experimental-logging-sanitization"}, args: []string{"--experimental-logging-sanitization"},
want: &Options{ want: func() *Options {
Config: config.LoggingConfiguration{ c := newOptions.Config.DeepCopy()
Format: DefaultLogFormat, c.Sanitization = true
Sanitization: true, return &Options{*c}
Options: NewOptions().Config.Options, }(),
},
},
}, },
{ {
name: "Unsupported log format", name: "Unsupported log format",
args: []string{"--logging-format=test"}, args: []string{"--logging-format=test"},
want: &Options{ want: func() *Options {
Config: config.LoggingConfiguration{ c := newOptions.Config.DeepCopy()
Format: "test", c.Format = "test"
Options: NewOptions().Config.Options, return &Options{*c}
}, }(),
},
errs: field.ErrorList{&field.Error{ errs: field.ErrorList{&field.Error{
Type: "FieldValueInvalid", Type: "FieldValueInvalid",
Field: "format", Field: "format",

View File

@ -18,6 +18,8 @@ package logs
import ( import (
"fmt" "fmt"
"math"
"strings"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
cliflag "k8s.io/component-base/cli/flag" cliflag "k8s.io/component-base/cli/flag"
@ -42,6 +44,26 @@ func ValidateLoggingConfiguration(c *config.LoggingConfiguration, fldPath *field
errs = append(errs, field.Invalid(fldPath.Child("format"), c.Format, "Unsupported log format")) errs = append(errs, field.Invalid(fldPath.Child("format"), c.Format, "Unsupported log format"))
} }
// The type in our struct is uint32, but klog only accepts positive int32.
if c.Verbosity > math.MaxInt32 {
errs = append(errs, field.Invalid(fldPath.Child("verbosity"), c.Verbosity, fmt.Sprintf("Must be <= %d", math.MaxInt32)))
}
vmoduleFldPath := fldPath.Child("vmodule")
if len(c.VModule) > 0 && c.Format != "" && c.Format != "text" {
errs = append(errs, field.Forbidden(vmoduleFldPath, "Only supported for text log format"))
}
for i, item := range c.VModule {
if item.FilePattern == "" {
errs = append(errs, field.Required(vmoduleFldPath.Index(i), "File pattern must not be empty"))
}
if strings.ContainsAny(item.FilePattern, "=,") {
errs = append(errs, field.Invalid(vmoduleFldPath.Index(i), item.FilePattern, "File pattern must not contain equal sign or comma"))
}
if item.Verbosity > math.MaxInt32 {
errs = append(errs, field.Invalid(vmoduleFldPath.Index(i), item.Verbosity, fmt.Sprintf("Must be <= %d", math.MaxInt32)))
}
}
// Currently nothing to validate for c.Options. // Currently nothing to validate for c.Options.
return errs return errs

View File

@ -0,0 +1,124 @@
/*
Copyright 2021 The Kubernetes 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 logs
import (
"math"
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/component-base/config"
)
func TestValidateLoggingConfiguration(t *testing.T) {
testcases := map[string]struct {
config config.LoggingConfiguration
expectErrors string
}{
"okay": {
config: config.LoggingConfiguration{
Format: "text",
Verbosity: 10,
VModule: config.VModuleConfiguration{
{
FilePattern: "gopher*",
Verbosity: 100,
},
},
},
},
"wrong-format": {
config: config.LoggingConfiguration{
Format: "no-such-format",
},
expectErrors: `format: Invalid value: "no-such-format": Unsupported log format`,
},
"verbosity-overflow": {
config: config.LoggingConfiguration{
Format: "text",
Verbosity: math.MaxInt32 + 1,
},
expectErrors: `verbosity: Invalid value: 0x80000000: Must be <= 2147483647`,
},
"vmodule-verbosity-overflow": {
config: config.LoggingConfiguration{
Format: "text",
VModule: config.VModuleConfiguration{
{
FilePattern: "gopher*",
Verbosity: math.MaxInt32 + 1,
},
},
},
expectErrors: `vmodule[0]: Invalid value: 0x80000000: Must be <= 2147483647`,
},
"vmodule-empty-pattern": {
config: config.LoggingConfiguration{
Format: "text",
VModule: config.VModuleConfiguration{
{
FilePattern: "",
Verbosity: 1,
},
},
},
expectErrors: `vmodule[0]: Required value: File pattern must not be empty`,
},
"vmodule-pattern-with-special-characters": {
config: config.LoggingConfiguration{
Format: "text",
VModule: config.VModuleConfiguration{
{
FilePattern: "foo,bar",
Verbosity: 1,
},
{
FilePattern: "foo=bar",
Verbosity: 1,
},
},
},
expectErrors: `[vmodule[0]: Invalid value: "foo,bar": File pattern must not contain equal sign or comma, vmodule[1]: Invalid value: "foo=bar": File pattern must not contain equal sign or comma]`,
},
"vmodule-unsupported": {
config: config.LoggingConfiguration{
Format: "json",
VModule: config.VModuleConfiguration{
{
FilePattern: "foo",
Verbosity: 1,
},
},
},
expectErrors: `[format: Invalid value: "json": Unsupported log format, vmodule: Forbidden: Only supported for text log format]`,
},
}
for name, test := range testcases {
t.Run(name, func(t *testing.T) {
errs := ValidateLoggingConfiguration(&test.config, nil)
if len(errs) == 0 {
if test.expectErrors != "" {
t.Fatalf("did not get expected error(s): %s", test.expectErrors)
}
} else {
assert.Equal(t, test.expectErrors, errs.ToAggregate().Error())
}
})
}
}