logs: make LoggingConfiguration an unversioned API

Making the LoggingConfiguration part of the versioned component-base/config API
had the theoretic advantage that components could have offered different
configuration APIs with experimental features limited to alpha versions (for
example, sanitization offered only in a v1alpha1.KubeletConfiguration). Some
components could have decided to only use stable logging options.

In practice, this wasn't done. Furthermore, we don't want different components
to make different choices regarding which logging features they offer to
users. It should always be the same everywhere, for the sake of consistency.

This can be achieved with a saner Go API by dropping the distinction between
internal and external LoggingConfiguration types. Different stability levels of
indidividual fields have to be covered by documentation (done) and potentially
feature gates (not currently done).

Advantages:

- everything related to logging is under component-base/logs;
  previously this was scattered across different packages and
  different files under "logs" (why some code was in logs/config.go
  vs. logs/options.go vs. logs/logs.go always confused me again
  and again when coming back to the code):

  - long-term config and command line API are clearly separated
    into the "api" package underneath that

  - logs/logs.go itself only deals with legacy global flags and
    logging configuration

- removal of separate Go APIs like logs.BindLoggingFlags and
  logs.Options

- LogRegistry becomes an implementation detail, with less code
  and less exported functionality (only registration needs to
  be exported, querying is internal)
This commit is contained in:
Patrick Ohly 2021-10-07 16:38:21 +02:00
parent 5fe00c14be
commit 1aceac797d
55 changed files with 993 additions and 1093 deletions

View File

@ -58,6 +58,7 @@ import (
cliflag "k8s.io/component-base/cli/flag"
"k8s.io/component-base/cli/globalflag"
"k8s.io/component-base/logs"
logsapi "k8s.io/component-base/logs/api/v1"
_ "k8s.io/component-base/metrics/prometheus/workqueue" // for workqueue metric registration
"k8s.io/component-base/term"
"k8s.io/component-base/version"
@ -82,7 +83,7 @@ import (
)
func init() {
utilruntime.Must(logs.AddFeatureGates(utilfeature.DefaultMutableFeatureGate))
utilruntime.Must(logsapi.AddFeatureGates(utilfeature.DefaultMutableFeatureGate))
}
// NewAPIServerCommand creates a *cobra.Command object with default parameters

View File

@ -55,6 +55,7 @@ import (
"k8s.io/component-base/cli/globalflag"
"k8s.io/component-base/configz"
"k8s.io/component-base/logs"
logsapi "k8s.io/component-base/logs/api/v1"
"k8s.io/component-base/term"
"k8s.io/component-base/version"
"k8s.io/component-base/version/verflag"
@ -75,7 +76,7 @@ import (
)
func init() {
utilruntime.Must(logs.AddFeatureGates(utilfeature.DefaultMutableFeatureGate))
utilruntime.Must(logsapi.AddFeatureGates(utilfeature.DefaultMutableFeatureGate))
}
const (

View File

@ -46,6 +46,7 @@ import (
"k8s.io/component-base/cli/globalflag"
"k8s.io/component-base/configz"
"k8s.io/component-base/logs"
logsapi "k8s.io/component-base/logs/api/v1"
"k8s.io/component-base/metrics/legacyregistry"
"k8s.io/component-base/term"
"k8s.io/component-base/version"
@ -62,7 +63,7 @@ import (
)
func init() {
utilruntime.Must(logs.AddFeatureGates(utilfeature.DefaultMutableFeatureGate))
utilruntime.Must(logsapi.AddFeatureGates(utilfeature.DefaultMutableFeatureGate))
}
// Option configures a framework.Registry.

View File

@ -28,7 +28,6 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
utilfeature "k8s.io/apiserver/pkg/util/feature"
cliflag "k8s.io/component-base/cli/flag"
"k8s.io/component-base/logs"
"k8s.io/kubelet/config/v1beta1"
kubeletapis "k8s.io/kubelet/pkg/apis"
"k8s.io/kubernetes/pkg/cluster/ports"
@ -500,7 +499,7 @@ func AddKubeletConfigFlags(mainfs *pflag.FlagSet, c *kubeletconfig.KubeletConfig
fs.StringSliceVar(&c.EnforceNodeAllocatable, "enforce-node-allocatable", c.EnforceNodeAllocatable, "A comma separated list of levels of node allocatable enforcement to be enforced by kubelet. Acceptable options are 'none', 'pods', 'system-reserved', and 'kube-reserved'. If the latter two options are specified, '--system-reserved-cgroup' and '--kube-reserved-cgroup' must also be set, respectively. If 'none' is specified, no additional options should be set. See https://kubernetes.io/docs/tasks/administer-cluster/reserve-compute-resources/ for more details.")
fs.StringVar(&c.SystemReservedCgroup, "system-reserved-cgroup", c.SystemReservedCgroup, "Absolute name of the top level cgroup that is used to manage non-kubernetes components for which compute resources were reserved via '--system-reserved' flag. Ex. '/system-reserved'. [default='']")
fs.StringVar(&c.KubeReservedCgroup, "kube-reserved-cgroup", c.KubeReservedCgroup, "Absolute name of the top level cgroup that is used to manage kubernetes components for which compute resources were reserved via '--kube-reserved' flag. Ex. '/kube-reserved'. [default='']")
logs.BindLoggingFlags(&c.Logging, fs)
c.Logging.AddFlags(fs)
// Memory Manager Flags
fs.StringVar(&c.MemoryManagerPolicy, "memory-manager-policy", c.MemoryManagerPolicy, "Memory Manager policy to use. Possible values: 'None', 'Static'.")

View File

@ -47,6 +47,7 @@ import (
utilnet "k8s.io/apimachinery/pkg/util/net"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apimachinery/pkg/util/wait"
genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/server/healthz"
@ -65,6 +66,7 @@ import (
"k8s.io/component-base/configz"
"k8s.io/component-base/featuregate"
"k8s.io/component-base/logs"
logsapi "k8s.io/component-base/logs/api/v1"
"k8s.io/component-base/metrics"
"k8s.io/component-base/metrics/legacyregistry"
"k8s.io/component-base/version"
@ -105,7 +107,7 @@ import (
)
func init() {
utilruntime.Must(logs.AddFeatureGates(utilfeature.DefaultMutableFeatureGate))
utilruntime.Must(logsapi.AddFeatureGates(utilfeature.DefaultMutableFeatureGate))
}
const (
@ -213,9 +215,8 @@ HTTP server: The kubelet can also listen for HTTP and respond to a simple API
// Config and flags parsed, now we can initialize logging.
logs.InitLogs()
logOption := &logs.Options{Config: kubeletConfig.Logging}
if err := logOption.ValidateAndApply(utilfeature.DefaultFeatureGate); err != nil {
return fmt.Errorf("failed to initialize logging: %v", err)
if err := kubeletConfig.Logging.ValidateAndApplyAsField(utilfeature.DefaultFeatureGate, field.NewPath("logging")); err != nil {
return fmt.Errorf("initialize logging: %v", err)
}
cliflag.PrintFlags(cleanFlagSet)

View File

@ -53339,7 +53339,7 @@ func schema_k8sio_kubelet_config_v1beta1_KubeletConfiguration(ref common.Referen
SchemaProps: spec.SchemaProps{
Description: "logging specifies the options of logging. Refer to [Logs Options](https://github.com/kubernetes/component-base/blob/master/logs/options.go) for more information. Default:\n Format: text",
Default: map[string]interface{}{},
Ref: ref("k8s.io/component-base/config/v1alpha1.LoggingConfiguration"),
Ref: ref("k8s.io/component-base/logs/api/v1.LoggingConfiguration"),
},
},
"enableSystemLogHandler": {
@ -53442,7 +53442,7 @@ func schema_k8sio_kubelet_config_v1beta1_KubeletConfiguration(ref common.Referen
},
},
Dependencies: []string{
"k8s.io/api/core/v1.Taint", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration", "k8s.io/component-base/config/v1alpha1.LoggingConfiguration", "k8s.io/kubelet/config/v1beta1.KubeletAuthentication", "k8s.io/kubelet/config/v1beta1.KubeletAuthorization", "k8s.io/kubelet/config/v1beta1.MemoryReservation", "k8s.io/kubelet/config/v1beta1.MemorySwapConfiguration", "k8s.io/kubelet/config/v1beta1.ShutdownGracePeriodByPodPriority"},
"k8s.io/api/core/v1.Taint", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration", "k8s.io/component-base/logs/api/v1.LoggingConfiguration", "k8s.io/kubelet/config/v1beta1.KubeletAuthentication", "k8s.io/kubelet/config/v1beta1.KubeletAuthorization", "k8s.io/kubelet/config/v1beta1.MemoryReservation", "k8s.io/kubelet/config/v1beta1.MemorySwapConfiguration", "k8s.io/kubelet/config/v1beta1.ShutdownGracePeriodByPodPriority"},
}
}

View File

@ -23,6 +23,7 @@ import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
componentconfigtesting "k8s.io/component-base/config/testing"
logsapi "k8s.io/component-base/logs/api/v1"
)
func TestComponentConfigSetup(t *testing.T) {
@ -32,10 +33,11 @@ func TestComponentConfigSetup(t *testing.T) {
SchemeGroupVersion: SchemeGroupVersion,
AddToScheme: AddToScheme,
AllowedTags: map[reflect.Type]bool{
reflect.TypeOf(metav1.TypeMeta{}): true,
reflect.TypeOf(metav1.Duration{}): true,
reflect.TypeOf(v1.NodeConfigSource{}): true,
reflect.TypeOf(v1.Taint{}): true,
reflect.TypeOf(logsapi.LoggingConfiguration{}): true,
reflect.TypeOf(metav1.Duration{}): true,
reflect.TypeOf(metav1.TypeMeta{}): true,
reflect.TypeOf(v1.NodeConfigSource{}): true,
reflect.TypeOf(v1.Taint{}): true,
},
}

View File

@ -19,7 +19,7 @@ package config
import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
componentbaseconfig "k8s.io/component-base/config"
logsapi "k8s.io/component-base/logs/api/v1"
)
// HairpinMode denotes how the kubelet should configure networking to handle
@ -384,7 +384,7 @@ type KubeletConfiguration struct {
ShowHiddenMetricsForVersion string
// Logging specifies the options of logging.
// Refer [Logs Options](https://github.com/kubernetes/component-base/blob/master/logs/options.go) for more information.
Logging componentbaseconfig.LoggingConfiguration
Logging logsapi.LoggingConfiguration
// EnableSystemLogHandler enables /logs handler.
EnableSystemLogHandler bool
// ShutdownGracePeriod specifies the total duration that the node should delay the shutdown and total grace period for pod termination during a node shutdown.

View File

@ -21,7 +21,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kruntime "k8s.io/apimachinery/pkg/runtime"
componentbaseconfigv1alpha1 "k8s.io/component-base/config/v1alpha1"
kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1"
// TODO: Cut references to k8s.io/kubernetes, eventually there should be none from this package
@ -245,7 +244,7 @@ func SetDefaults_KubeletConfiguration(obj *kubeletconfigv1beta1.KubeletConfigura
obj.VolumePluginDir = DefaultVolumePluginDir
}
// Use the Default LoggingConfiguration option
componentbaseconfigv1alpha1.RecommendedLoggingConfiguration(&obj.Logging)
obj.Logging.SetRecommendedLoggingConfiguration()
if obj.EnableSystemLogHandler == nil {
obj.EnableSystemLogHandler = utilpointer.BoolPtr(true)
}

View File

@ -24,7 +24,7 @@ import (
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
componentbaseconfigv1alpha1 "k8s.io/component-base/config/v1alpha1"
logsapi "k8s.io/component-base/logs/api/v1"
"k8s.io/kubelet/config/v1beta1"
"k8s.io/kubernetes/pkg/cluster/ports"
"k8s.io/kubernetes/pkg/kubelet/qos"
@ -111,7 +111,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) {
ConfigMapAndSecretChangeDetectionStrategy: v1beta1.WatchChangeDetectionStrategy,
EnforceNodeAllocatable: DefaultNodeAllocatableEnforcement,
VolumePluginDir: DefaultVolumePluginDir,
Logging: componentbaseconfigv1alpha1.LoggingConfiguration{
Logging: logsapi.LoggingConfiguration{
Format: "text",
FlushFrequency: 5 * time.Second,
},
@ -232,7 +232,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) {
VolumePluginDir: "",
ProviderID: "",
KernelMemcgNotification: false,
Logging: componentbaseconfigv1alpha1.LoggingConfiguration{
Logging: logsapi.LoggingConfiguration{
Format: "",
FlushFrequency: 5 * time.Second,
},
@ -329,7 +329,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) {
EnforceNodeAllocatable: []string{},
AllowedUnsafeSysctls: []string{},
VolumePluginDir: DefaultVolumePluginDir,
Logging: componentbaseconfigv1alpha1.LoggingConfiguration{
Logging: logsapi.LoggingConfiguration{
Format: "text",
FlushFrequency: 5 * time.Second,
},
@ -468,7 +468,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) {
VolumePluginDir: "volume-plugin-dir",
ProviderID: "provider-id",
KernelMemcgNotification: true,
Logging: componentbaseconfigv1alpha1.LoggingConfiguration{
Logging: logsapi.LoggingConfiguration{
Format: "json",
FlushFrequency: 5 * time.Second,
},
@ -611,7 +611,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) {
VolumePluginDir: "volume-plugin-dir",
ProviderID: "provider-id",
KernelMemcgNotification: true,
Logging: componentbaseconfigv1alpha1.LoggingConfiguration{
Logging: logsapi.LoggingConfiguration{
Format: "json",
FlushFrequency: 5 * time.Second,
},
@ -705,7 +705,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) {
ConfigMapAndSecretChangeDetectionStrategy: v1beta1.WatchChangeDetectionStrategy,
EnforceNodeAllocatable: DefaultNodeAllocatableEnforcement,
VolumePluginDir: DefaultVolumePluginDir,
Logging: componentbaseconfigv1alpha1.LoggingConfiguration{
Logging: logsapi.LoggingConfiguration{
Format: "text",
FlushFrequency: 5 * time.Second,
},

View File

@ -28,7 +28,6 @@ import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
v1alpha1 "k8s.io/component-base/config/v1alpha1"
v1beta1 "k8s.io/kubelet/config/v1beta1"
config "k8s.io/kubernetes/pkg/kubelet/apis/config"
)
@ -485,9 +484,7 @@ func autoConvert_v1beta1_KubeletConfiguration_To_config_KubeletConfiguration(in
out.VolumePluginDir = in.VolumePluginDir
out.ProviderID = in.ProviderID
out.KernelMemcgNotification = in.KernelMemcgNotification
if err := v1alpha1.Convert_v1alpha1_LoggingConfiguration_To_config_LoggingConfiguration(&in.Logging, &out.Logging, s); err != nil {
return err
}
out.Logging = in.Logging
if err := v1.Convert_Pointer_bool_To_bool(&in.EnableSystemLogHandler, &out.EnableSystemLogHandler, s); err != nil {
return err
}
@ -661,9 +658,7 @@ func autoConvert_config_KubeletConfiguration_To_v1beta1_KubeletConfiguration(in
out.EnforceNodeAllocatable = *(*[]string)(unsafe.Pointer(&in.EnforceNodeAllocatable))
out.ReservedSystemCPUs = in.ReservedSystemCPUs
out.ShowHiddenMetricsForVersion = in.ShowHiddenMetricsForVersion
if err := v1alpha1.Convert_config_LoggingConfiguration_To_v1alpha1_LoggingConfiguration(&in.Logging, &out.Logging, s); err != nil {
return err
}
out.Logging = in.Logging
if err := v1.Convert_bool_To_Pointer_bool(&in.EnableSystemLogHandler, &out.EnableSystemLogHandler, s); err != nil {
return err
}

View File

@ -25,7 +25,6 @@ import (
utilvalidation "k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/component-base/logs"
"k8s.io/component-base/metrics"
"k8s.io/kubernetes/pkg/features"
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
@ -237,7 +236,7 @@ func ValidateKubeletConfiguration(kc *kubeletconfig.KubeletConfiguration) error
}
allErrors = append(allErrors, metrics.ValidateShowHiddenMetricsVersion(kc.ShowHiddenMetricsForVersion)...)
if errs := logs.ValidateLoggingConfiguration(&kc.Logging, field.NewPath("logging")); len(errs) > 0 {
if errs := kc.Logging.Validate(localFeatureGate, field.NewPath("logging")); len(errs) > 0 {
allErrors = append(allErrors, errs.ToAggregate().Errors()...)
}

View File

@ -23,7 +23,7 @@ import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
componentbaseconfig "k8s.io/component-base/config"
logsapi "k8s.io/component-base/logs/api/v1"
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
"k8s.io/kubernetes/pkg/kubelet/apis/config/validation"
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
@ -68,7 +68,7 @@ var (
"GracefulNodeShutdown": true,
"MemoryQoS": true,
},
Logging: componentbaseconfig.LoggingConfiguration{
Logging: logsapi.LoggingConfiguration{
Format: "text",
},
}

View File

@ -17,14 +17,6 @@ limitations under the License.
package config
import (
"fmt"
"strconv"
"strings"
"time"
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -86,127 +78,3 @@ type DebuggingConfiguration struct {
// enableProfiling is true.
EnableContentionProfiling bool
}
// LoggingConfiguration contains logging options
// Refer [Logs Options](https://github.com/kubernetes/component-base/blob/master/logs/options.go) for more information.
type LoggingConfiguration struct {
// Format Flag specifies the structure of log messages.
// default value of format is `text`
Format string
// Maximum number of nanoseconds (i.e. 1s = 1000000000) 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] Options holds additional parameters that are specific
// to the different logging formats. Only the options for the selected
// format get used, but all of them get validated.
Options FormatOptions
}
// FormatOptions contains options for the different logging formats.
type FormatOptions struct {
// [Experimental] JSON contains options for logging format "json".
JSON JSONOptions
}
// JSONOptions contains options for logging format "json".
type JSONOptions struct {
// [Experimental] SplitStream redirects error messages to stderr while
// info messages go to stdout, with buffering. The default is to write
// both to stdout, without buffering.
SplitStream bool
// [Experimental] InfoBufferSize sets the size of the info stream when
// using split streams. The default is zero, which disables buffering.
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

@ -1,119 +0,0 @@
/*
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

@ -51,11 +51,3 @@ func Convert_v1alpha1_LeaderElectionConfiguration_To_config_LeaderElectionConfig
func Convert_config_LeaderElectionConfiguration_To_v1alpha1_LeaderElectionConfiguration(in *config.LeaderElectionConfiguration, out *LeaderElectionConfiguration, s conversion.Scope) error {
return autoConvert_config_LeaderElectionConfiguration_To_v1alpha1_LeaderElectionConfiguration(in, out, s)
}
func Convert_v1alpha1_LoggingConfiguration_To_config_LoggingConfiguration(in *LoggingConfiguration, out *config.LoggingConfiguration, s conversion.Scope) error {
return autoConvert_v1alpha1_LoggingConfiguration_To_config_LoggingConfiguration(in, out, s)
}
func Convert_config_LoggingConfiguration_To_v1alpha1_LoggingConfiguration(in *config.LoggingConfiguration, out *LoggingConfiguration, s conversion.Scope) error {
return autoConvert_config_LoggingConfiguration_To_v1alpha1_LoggingConfiguration(in, out, s)
}

View File

@ -19,7 +19,6 @@ package v1alpha1
import (
"time"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilpointer "k8s.io/utils/pointer"
)
@ -97,32 +96,3 @@ func NewRecommendedDebuggingConfiguration() *DebuggingConfiguration {
RecommendedDebuggingConfiguration(ret)
return ret
}
// RecommendedLoggingConfiguration defaults logging configuration.
// This will set the recommended default
// values, but they may be subject to change between API versions. This function
// is intentionally not registered in the scheme as a "normal" `SetDefaults_Foo`
// function to allow consumers of this type to set whatever defaults for their
// embedded configs. Forcing consumers to use these defaults would be problematic
// as defaulting in the scheme is done as part of the conversion, and there would
// be no easy way to opt-out. Instead, if you want to use this defaulting method
// run it in your wrapper struct of this type in its `SetDefaults_` method.
func RecommendedLoggingConfiguration(obj *LoggingConfiguration) {
if obj.Format == "" {
obj.Format = "text"
}
var empty resource.QuantityValue
if obj.Options.JSON.InfoBufferSize == empty {
obj.Options.JSON.InfoBufferSize = resource.QuantityValue{
// This is similar, but not quite the same as a default
// constructed instance.
Quantity: *resource.NewQuantity(0, resource.DecimalSI),
}
// This sets the unexported Quantity.s which will be compared
// by reflect.DeepEqual in some tests.
_ = obj.Options.JSON.InfoBufferSize.String()
}
if obj.FlushFrequency == 0 {
obj.FlushFrequency = 5 * time.Second
}
}

View File

@ -17,9 +17,6 @@ limitations under the License.
package v1alpha1
import (
"time"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -83,61 +80,3 @@ type ClientConnectionConfiguration struct {
// burst allows extra queries to accumulate when a client is exceeding its rate.
Burst int32 `json:"burst"`
}
// LoggingConfiguration contains logging options
// Refer [Logs Options](https://github.com/kubernetes/component-base/blob/master/logs/options.go) for more information.
type LoggingConfiguration struct {
// Format Flag specifies the structure of log messages.
// default value of format is `text`
Format string `json:"format,omitempty"`
// Maximum number of nanoseconds (i.e. 1s = 1000000000) 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] Options holds additional parameters that are specific
// to the different logging formats. Only the options for the selected
// format get used, but all of them get validated.
Options FormatOptions `json:"options,omitempty"`
}
// FormatOptions contains options for the different logging formats.
type FormatOptions struct {
// [Experimental] JSON contains options for logging format "json".
JSON JSONOptions `json:"json,omitempty"`
}
// JSONOptions contains options for logging format "json".
type JSONOptions struct {
// [Experimental] SplitStream redirects error messages to stderr while
// info messages go to stdout, with buffering. The default is to write
// both to stdout, without buffering.
SplitStream bool `json:"splitStream,omitempty"`
// [Experimental] InfoBufferSize sets the size of the info stream when
// using split streams. The default is zero, which disables buffering.
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,9 +22,6 @@ limitations under the License.
package v1alpha1
import (
time "time"
unsafe "unsafe"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
@ -38,36 +35,6 @@ func init() {
// RegisterConversions adds conversion functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterConversions(s *runtime.Scheme) error {
if err := s.AddGeneratedConversionFunc((*FormatOptions)(nil), (*config.FormatOptions)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_FormatOptions_To_config_FormatOptions(a.(*FormatOptions), b.(*config.FormatOptions), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*config.FormatOptions)(nil), (*FormatOptions)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_FormatOptions_To_v1alpha1_FormatOptions(a.(*config.FormatOptions), b.(*FormatOptions), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*JSONOptions)(nil), (*config.JSONOptions)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_JSONOptions_To_config_JSONOptions(a.(*JSONOptions), b.(*config.JSONOptions), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*config.JSONOptions)(nil), (*JSONOptions)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_JSONOptions_To_v1alpha1_JSONOptions(a.(*config.JSONOptions), b.(*JSONOptions), scope)
}); err != nil {
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 {
return Convert_config_ClientConnectionConfiguration_To_v1alpha1_ClientConnectionConfiguration(a.(*config.ClientConnectionConfiguration), b.(*ClientConnectionConfiguration), scope)
}); err != nil {
@ -83,11 +50,6 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddConversionFunc((*config.LoggingConfiguration)(nil), (*LoggingConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_LoggingConfiguration_To_v1alpha1_LoggingConfiguration(a.(*config.LoggingConfiguration), b.(*LoggingConfiguration), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*ClientConnectionConfiguration)(nil), (*config.ClientConnectionConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_ClientConnectionConfiguration_To_config_ClientConnectionConfiguration(a.(*ClientConnectionConfiguration), b.(*config.ClientConnectionConfiguration), scope)
}); err != nil {
@ -103,11 +65,6 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddConversionFunc((*LoggingConfiguration)(nil), (*config.LoggingConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_LoggingConfiguration_To_config_LoggingConfiguration(a.(*LoggingConfiguration), b.(*config.LoggingConfiguration), scope)
}); err != nil {
return err
}
return nil
}
@ -149,52 +106,6 @@ func autoConvert_config_DebuggingConfiguration_To_v1alpha1_DebuggingConfiguratio
return nil
}
func autoConvert_v1alpha1_FormatOptions_To_config_FormatOptions(in *FormatOptions, out *config.FormatOptions, s conversion.Scope) error {
if err := Convert_v1alpha1_JSONOptions_To_config_JSONOptions(&in.JSON, &out.JSON, s); err != nil {
return err
}
return nil
}
// Convert_v1alpha1_FormatOptions_To_config_FormatOptions is an autogenerated conversion function.
func Convert_v1alpha1_FormatOptions_To_config_FormatOptions(in *FormatOptions, out *config.FormatOptions, s conversion.Scope) error {
return autoConvert_v1alpha1_FormatOptions_To_config_FormatOptions(in, out, s)
}
func autoConvert_config_FormatOptions_To_v1alpha1_FormatOptions(in *config.FormatOptions, out *FormatOptions, s conversion.Scope) error {
if err := Convert_config_JSONOptions_To_v1alpha1_JSONOptions(&in.JSON, &out.JSON, s); err != nil {
return err
}
return nil
}
// Convert_config_FormatOptions_To_v1alpha1_FormatOptions is an autogenerated conversion function.
func Convert_config_FormatOptions_To_v1alpha1_FormatOptions(in *config.FormatOptions, out *FormatOptions, s conversion.Scope) error {
return autoConvert_config_FormatOptions_To_v1alpha1_FormatOptions(in, out, s)
}
func autoConvert_v1alpha1_JSONOptions_To_config_JSONOptions(in *JSONOptions, out *config.JSONOptions, s conversion.Scope) error {
out.SplitStream = in.SplitStream
out.InfoBufferSize = in.InfoBufferSize
return nil
}
// Convert_v1alpha1_JSONOptions_To_config_JSONOptions is an autogenerated conversion function.
func Convert_v1alpha1_JSONOptions_To_config_JSONOptions(in *JSONOptions, out *config.JSONOptions, s conversion.Scope) error {
return autoConvert_v1alpha1_JSONOptions_To_config_JSONOptions(in, out, s)
}
func autoConvert_config_JSONOptions_To_v1alpha1_JSONOptions(in *config.JSONOptions, out *JSONOptions, s conversion.Scope) error {
out.SplitStream = in.SplitStream
out.InfoBufferSize = in.InfoBufferSize
return nil
}
// Convert_config_JSONOptions_To_v1alpha1_JSONOptions is an autogenerated conversion function.
func Convert_config_JSONOptions_To_v1alpha1_JSONOptions(in *config.JSONOptions, out *JSONOptions, s conversion.Scope) error {
return autoConvert_config_JSONOptions_To_v1alpha1_JSONOptions(in, out, s)
}
func autoConvert_v1alpha1_LeaderElectionConfiguration_To_config_LeaderElectionConfiguration(in *LeaderElectionConfiguration, out *config.LeaderElectionConfiguration, s conversion.Scope) error {
if err := v1.Convert_Pointer_bool_To_bool(&in.LeaderElect, &out.LeaderElect, s); err != nil {
return err
@ -220,47 +131,3 @@ func autoConvert_config_LeaderElectionConfiguration_To_v1alpha1_LeaderElectionCo
out.ResourceNamespace = in.ResourceNamespace
return nil
}
func autoConvert_v1alpha1_LoggingConfiguration_To_config_LoggingConfiguration(in *LoggingConfiguration, out *config.LoggingConfiguration, s conversion.Scope) error {
out.Format = in.Format
out.FlushFrequency = time.Duration(in.FlushFrequency)
out.Verbosity = config.VerbosityLevel(in.Verbosity)
out.VModule = *(*config.VModuleConfiguration)(unsafe.Pointer(&in.VModule))
if err := Convert_v1alpha1_FormatOptions_To_config_FormatOptions(&in.Options, &out.Options, s); err != nil {
return err
}
return nil
}
func autoConvert_config_LoggingConfiguration_To_v1alpha1_LoggingConfiguration(in *config.LoggingConfiguration, out *LoggingConfiguration, s conversion.Scope) error {
out.Format = in.Format
out.FlushFrequency = time.Duration(in.FlushFrequency)
out.Verbosity = uint32(in.Verbosity)
out.VModule = *(*VModuleConfiguration)(unsafe.Pointer(&in.VModule))
if err := Convert_config_FormatOptions_To_v1alpha1_FormatOptions(&in.Options, &out.Options, s); err != nil {
return err
}
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

@ -63,40 +63,6 @@ func (in *DebuggingConfiguration) DeepCopy() *DebuggingConfiguration {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FormatOptions) DeepCopyInto(out *FormatOptions) {
*out = *in
in.JSON.DeepCopyInto(&out.JSON)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FormatOptions.
func (in *FormatOptions) DeepCopy() *FormatOptions {
if in == nil {
return nil
}
out := new(FormatOptions)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JSONOptions) DeepCopyInto(out *JSONOptions) {
*out = *in
in.InfoBufferSize.DeepCopyInto(&out.InfoBufferSize)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONOptions.
func (in *JSONOptions) DeepCopy() *JSONOptions {
if in == nil {
return nil
}
out := new(JSONOptions)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LeaderElectionConfiguration) DeepCopyInto(out *LeaderElectionConfiguration) {
*out = *in
@ -120,61 +86,3 @@ func (in *LeaderElectionConfiguration) DeepCopy() *LeaderElectionConfiguration {
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LoggingConfiguration) DeepCopyInto(out *LoggingConfiguration) {
*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)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoggingConfiguration.
func (in *LoggingConfiguration) DeepCopy() *LoggingConfiguration {
if in == nil {
return nil
}
out := new(LoggingConfiguration)
in.DeepCopyInto(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

@ -53,40 +53,6 @@ func (in *DebuggingConfiguration) DeepCopy() *DebuggingConfiguration {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FormatOptions) DeepCopyInto(out *FormatOptions) {
*out = *in
in.JSON.DeepCopyInto(&out.JSON)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FormatOptions.
func (in *FormatOptions) DeepCopy() *FormatOptions {
if in == nil {
return nil
}
out := new(FormatOptions)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JSONOptions) DeepCopyInto(out *JSONOptions) {
*out = *in
in.InfoBufferSize.DeepCopyInto(&out.InfoBufferSize)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONOptions.
func (in *JSONOptions) DeepCopy() *JSONOptions {
if in == nil {
return nil
}
out := new(JSONOptions)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LeaderElectionConfiguration) DeepCopyInto(out *LeaderElectionConfiguration) {
*out = *in
@ -105,61 +71,3 @@ func (in *LeaderElectionConfiguration) DeepCopy() *LeaderElectionConfiguration {
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LoggingConfiguration) DeepCopyInto(out *LoggingConfiguration) {
*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)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoggingConfiguration.
func (in *LoggingConfiguration) DeepCopy() *LoggingConfiguration {
if in == nil {
return nil
}
out := new(LoggingConfiguration)
in.DeepCopyInto(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

@ -28,6 +28,7 @@ require (
k8s.io/client-go v0.0.0
k8s.io/klog/v2 v2.60.1
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2
)
require (
@ -81,7 +82,6 @@ require (
gotest.tools/v3 v3.0.3 // indirect
k8s.io/api v0.0.0 // indirect
k8s.io/kube-openapi v0.0.0-20220603121420-31174f50af60 // indirect
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
sigs.k8s.io/yaml v1.2.0 // indirect
)

View File

@ -0,0 +1,9 @@
# Disable inheritance as this is an api owners file
options:
no_parent_owners: true
approvers:
- api-approvers
reviewers:
- api-reviewers
labels:
- kind/api-change

View File

@ -0,0 +1,32 @@
/*
Copyright 2022 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.
*/
// +k8s:deepcopy-gen=package
// Package v1 contains the configuration API for logging.
//
// The intention is to only have a single version of this API, potentially with
// new fields added over time in a backwards-compatible manner. Fields for
// alpha or beta features are allowed as long as they are defined so that not
// changing the defaults leaves those features disabled.
//
// The "v1" package name is just a reminder that API compatibility rules apply,
// not an indication of the stability of all features covered by it.
// The LoggingAlphaOptions and LoggingBetaOptions feature gates control whether
// these unstable features can get enabled. This can be used to ensure that
// command invocations do not accidentally rely on unstable features.
package v1 // import "k8s.io/component-base/logs/api/v1"

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package logs
package v1
import (
"k8s.io/component-base/featuregate"

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 The Kubernetes Authors.
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.
@ -14,33 +14,44 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package logs
package v1
import (
"flag"
"fmt"
"math"
"sort"
"strings"
"time"
"github.com/spf13/pflag"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/component-base/config"
"k8s.io/component-base/config/v1alpha1"
"k8s.io/component-base/featuregate"
"k8s.io/component-base/logs/registry"
"k8s.io/klog/v2"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/util/validation/field"
cliflag "k8s.io/component-base/cli/flag"
"k8s.io/component-base/featuregate"
)
// Options has klog format parameters
type Options struct {
Config config.LoggingConfiguration
}
const (
// LogFlushFreqDefault is the default for the corresponding command line
// parameter.
LogFlushFreqDefault = 5 * time.Second
)
// NewOptions return new klog options
func NewOptions() *Options {
c := v1alpha1.LoggingConfiguration{}
v1alpha1.RecommendedLoggingConfiguration(&c)
o := &Options{}
v1alpha1.Convert_v1alpha1_LoggingConfiguration_To_config_LoggingConfiguration(&c, &o.Config, nil)
return o
const (
// LogFlushFreqFlagName is the name of the command line parameter.
// Depending on how flags get added, it is either a stand-alone
// value (logs.AddFlags) or part of LoggingConfiguration.
LogFlushFreqFlagName = "log-flush-frequency"
)
// NewLoggingConfiguration returns a struct holding the default logging configuration.
func NewLoggingConfiguration() *LoggingConfiguration {
c := LoggingConfiguration{}
c.SetRecommendedLoggingConfiguration()
return &c
}
// ValidateAndApply combines validation and application of the logging configuration.
@ -50,58 +61,186 @@ func NewOptions() *Options {
//
// The optional FeatureGate controls logging features. If nil, the default for
// these features is used.
func (o *Options) ValidateAndApply(featureGate featuregate.FeatureGate) error {
errs := o.validate()
func (c *LoggingConfiguration) ValidateAndApply(featureGate featuregate.FeatureGate) error {
return c.ValidateAndApplyAsField(featureGate, nil)
}
// ValidateAndApplyAsField is a variant of ValidateAndApply that should be used
// when the LoggingConfiguration is embedded in some larger configuration
// structure.
func (c *LoggingConfiguration) ValidateAndApplyAsField(featureGate featuregate.FeatureGate, fldPath *field.Path) error {
errs := c.Validate(featureGate, fldPath)
if len(errs) > 0 {
return utilerrors.NewAggregate(errs)
return errs.ToAggregate()
}
o.apply(featureGate)
return nil
return c.apply(featureGate)
}
// validate verifies if any unsupported flag is set
// for non-default logging format
func (o *Options) validate() []error {
errs := ValidateLoggingConfiguration(&o.Config, nil)
if len(errs) != 0 {
return errs.ToAggregate().Errors()
// Validate can be used to check for invalid settings without applying them.
// Most binaries should validate and apply the logging configuration as soon
// as possible via ValidateAndApply. The field path is optional: nil
// can be passed when the struct is not embedded in some larger struct.
func (c *LoggingConfiguration) Validate(featureGate featuregate.FeatureGate, fldPath *field.Path) field.ErrorList {
errs := field.ErrorList{}
if c.Format != DefaultLogFormat {
// WordSepNormalizeFunc is just a guess. Commands should use it,
// but we cannot know for sure.
allFlags := unsupportedLoggingFlags(cliflag.WordSepNormalizeFunc)
for _, f := range allFlags {
if f.DefValue != f.Value.String() {
errs = append(errs, field.Invalid(fldPath.Child("format"), c.Format, fmt.Sprintf("Non-default format doesn't honor flag: %s", f.Name)))
}
}
}
return nil
if _, err := logRegistry.get(c.Format); err != nil {
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.
return errs
}
// 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) {
BindLoggingFlags(&o.Config, fs)
}
// apply set klog logger from LogFormat type
func (o *Options) apply(featureGate featuregate.FeatureGate) {
func (c *LoggingConfiguration) apply(featureGate featuregate.FeatureGate) error {
contextualLoggingEnabled := contextualLoggingDefault
if featureGate != nil {
contextualLoggingEnabled = featureGate.Enabled(ContextualLogging)
}
// if log format not exists, use nil loggr
factory, _ := registry.LogRegistry.Get(o.Config.Format)
factory, _ := logRegistry.get(c.Format)
if factory == nil {
klog.ClearLogger()
} else {
// This logger will do its own verbosity checking, using the exact same
// configuration as klog itself.
log, flush := factory.Create(o.Config)
// Therefore it can get called directly. However, we only allow that
// when the feature is enabled.
log, flush := factory.Create(*c)
klog.SetLoggerWithOptions(log, klog.ContextualLogger(contextualLoggingEnabled), klog.FlushLogger(flush))
}
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("v").Value.Set(VerbosityLevelPflag(&c.Verbosity).String()); err != nil {
return 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))
if err := loggingFlags.Lookup("vmodule").Value.Set(VModuleConfigurationPflag(&c.VModule).String()); err != nil {
return fmt.Errorf("internal error while setting klog vmodule: %v", err)
}
klog.StartFlushDaemon(o.Config.FlushFrequency)
klog.StartFlushDaemon(c.FlushFrequency)
klog.EnableContextualLogging(contextualLoggingEnabled)
return nil
}
// AddFlags adds command line flags for the configuration.
func (c *LoggingConfiguration) AddFlags(fs *pflag.FlagSet) {
// The help text is generated assuming that flags will eventually use
// hyphens, even if currently no normalization function is set for the
// flag set yet.
unsupportedFlags := strings.Join(unsupportedLoggingFlagNames(cliflag.WordSepNormalizeFunc), ", ")
formats := fmt.Sprintf(`"%s"`, strings.Join(logRegistry.list(), `", "`))
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
logRegistry.freeze()
fs.DurationVar(&c.FlushFrequency, LogFlushFreqFlagName, c.FlushFrequency, "Maximum number of seconds between log flushes")
fs.VarP(VerbosityLevelPflag(&c.Verbosity), "v", "v", "number for the log level verbosity")
fs.Var(VModuleConfigurationPflag(&c.VModule), "vmodule", "comma-separated list of pattern=N settings for file-filtered logging (only works for text log format)")
// JSON options. We only register them if "json" is a valid format. The
// config file API however always has them.
if _, err := logRegistry.get("json"); err == nil {
fs.BoolVar(&c.Options.JSON.SplitStream, "log-json-split-stream", false, "[Experimental] In JSON format, write error messages to stderr and info messages to stdout. The default is to write a single stream to stdout.")
fs.Var(&c.Options.JSON.InfoBufferSize, "log-json-info-buffer-size", "[Experimental] In JSON format with split output streams, the info messages can be buffered for a while to increase performance. The default value of zero bytes disables buffering. The size can be specified as number of bytes (512), multiples of 1000 (1K), multiples of 1024 (2Ki), or powers of those (3M, 4G, 5Mi, 6Gi).")
}
}
// SetRecommendedLoggingConfiguration sets the default logging configuration
// for fields that are unset.
//
// Consumers who embed LoggingConfiguration in their own configuration structs
// may set custom defaults and then should call this function to add the
// global defaults.
func (c *LoggingConfiguration) SetRecommendedLoggingConfiguration() {
if c.Format == "" {
c.Format = "text"
}
if c.FlushFrequency == 0 {
c.FlushFrequency = LogFlushFreqDefault
}
var empty resource.QuantityValue
if c.Options.JSON.InfoBufferSize == empty {
c.Options.JSON.InfoBufferSize = resource.QuantityValue{
// This is similar, but not quite the same as a default
// constructed instance.
Quantity: *resource.NewQuantity(0, resource.DecimalSI),
}
// This sets the unexported Quantity.s which will be compared
// by reflect.DeepEqual in some tests.
_ = c.Options.JSON.InfoBufferSize.String()
}
}
// loggingFlags captures the state of the logging flags, in particular their default value
// before flag parsing. It is used by unsupportedLoggingFlags.
var loggingFlags pflag.FlagSet
func init() {
var fs flag.FlagSet
klog.InitFlags(&fs)
loggingFlags.AddGoFlagSet(&fs)
}
// List of logs (k8s.io/klog + k8s.io/component-base/logs) flags supported by all logging formats
var supportedLogsFlags = map[string]struct{}{
"v": {},
// TODO: support vmodule after 1.19 Alpha
}
// unsupportedLoggingFlags lists unsupported logging flags. The normalize
// function is optional.
func unsupportedLoggingFlags(normalizeFunc func(f *pflag.FlagSet, name string) pflag.NormalizedName) []*pflag.Flag {
// k8s.io/component-base/logs and klog flags
pfs := &pflag.FlagSet{}
loggingFlags.VisitAll(func(flag *pflag.Flag) {
if _, found := supportedLogsFlags[flag.Name]; !found {
// Normalization changes flag.Name, so make a copy.
clone := *flag
pfs.AddFlag(&clone)
}
})
// Apply normalization.
pfs.SetNormalizeFunc(normalizeFunc)
var allFlags []*pflag.Flag
pfs.VisitAll(func(flag *pflag.Flag) {
allFlags = append(allFlags, flag)
})
return allFlags
}
// unsupportedLoggingFlagNames lists unsupported logging flags by name, with
// optional normalization and sorted.
func unsupportedLoggingFlagNames(normalizeFunc func(f *pflag.FlagSet, name string) pflag.NormalizedName) []string {
unsupportedFlags := unsupportedLoggingFlags(normalizeFunc)
names := make([]string, 0, len(unsupportedFlags))
for _, f := range unsupportedFlags {
names = append(names, "--"+f.Name)
}
sort.Strings(names)
return names
}

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package logs
package v1
import (
"bytes"
@ -32,10 +32,10 @@ import (
)
func TestFlags(t *testing.T) {
o := NewOptions()
c := NewLoggingConfiguration()
fs := pflag.NewFlagSet("addflagstest", pflag.ContinueOnError)
output := bytes.Buffer{}
o.AddFlags(fs)
c.AddFlags(fs)
fs.SetOutput(&output)
fs.PrintDefaults()
want := ` --log-flush-frequency duration Maximum number of seconds between log flushes (default 5s)
@ -51,29 +51,30 @@ func TestFlags(t *testing.T) {
}
func TestOptions(t *testing.T) {
newOptions := NewOptions()
newOptions := NewLoggingConfiguration()
testcases := []struct {
name string
args []string
want *Options
path *field.Path
want *LoggingConfiguration
errs field.ErrorList
}{
{
name: "Default log format",
want: newOptions,
want: newOptions.DeepCopy(),
},
{
name: "Text log format",
args: []string{"--logging-format=text"},
want: newOptions,
want: newOptions.DeepCopy(),
},
{
name: "Unsupported log format",
args: []string{"--logging-format=test"},
want: func() *Options {
c := newOptions.Config.DeepCopy()
want: func() *LoggingConfiguration {
c := newOptions.DeepCopy()
c.Format = "test"
return &Options{*c}
return c
}(),
errs: field.ErrorList{&field.Error{
Type: "FieldValueInvalid",
@ -86,19 +87,17 @@ func TestOptions(t *testing.T) {
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
o := NewOptions()
c := NewLoggingConfiguration()
fs := pflag.NewFlagSet("addflagstest", pflag.ContinueOnError)
o.AddFlags(fs)
c.AddFlags(fs)
fs.Parse(tc.args)
if !assert.Equal(t, tc.want, o) {
t.Errorf("Wrong Validate() result for %q. expect %v, got %v", tc.name, tc.want, o)
if !assert.Equal(t, tc.want, c) {
t.Errorf("Wrong Validate() result for %q. expect %v, got %v", tc.name, tc.want, c)
}
err := o.ValidateAndApply(nil /* We don't care about feature gates here. */)
errs := c.ValidateAndApply(nil /* We don't care about feature gates here. */)
defer klog.StopFlushDaemon()
if !assert.ElementsMatch(t, tc.errs.ToAggregate(), err) {
t.Errorf("Wrong Validate() result for %q.\n expect:\t%+v\n got:\t%+v", tc.name, tc.errs, err)
if !assert.ElementsMatch(t, tc.errs, errs) {
t.Errorf("Wrong Validate() result for %q.\n expect:\t%+v\n got:\t%+v", tc.name, tc.errs, errs)
}
})
}
@ -117,12 +116,12 @@ func TestContextualLogging(t *testing.T) {
func testContextualLogging(t *testing.T, enabled bool) {
var err error
o := NewOptions()
c := NewLoggingConfiguration()
featureGate := featuregate.NewFeatureGate()
AddFeatureGates(featureGate)
err = featureGate.SetFromMap(map[string]bool{string(ContextualLogging): enabled})
require.NoError(t, err)
err = o.ValidateAndApply(featureGate)
err = c.ValidateAndApply(featureGate)
require.NoError(t, err)
defer klog.StopFlushDaemon()
defer klog.EnableContextualLogging(true)

View File

@ -0,0 +1,104 @@
/*
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 v1
import (
"fmt"
"strconv"
"strings"
"github.com/spf13/pflag"
)
// VModuleConfigurationPflag implements the pflag.Value interface for a
// VModuleConfiguration. The value pointer must not be nil.
func VModuleConfigurationPflag(value *VModuleConfiguration) pflag.Value {
return vmoduleConfigurationPFlag{value}
}
type vmoduleConfigurationPFlag struct {
value *VModuleConfiguration
}
// String returns the -vmodule parameter (comma-separated list of pattern=N).
func (wrapper vmoduleConfigurationPFlag) String() string {
var patterns []string
for _, item := range *wrapper.value {
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 (wrapper vmoduleConfigurationPFlag) 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)
}
*wrapper.value = append(*wrapper.value, VModuleItem{FilePattern: pattern, Verbosity: VerbosityLevel(v)})
}
return nil
}
func (wrapper vmoduleConfigurationPFlag) Type() string {
return "pattern=N,..."
}
// VerbosityLevelPflag implements the pflag.Value interface for a verbosity
// level value.
func VerbosityLevelPflag(value *VerbosityLevel) pflag.Value {
return verbosityLevelPflag{value}
}
type verbosityLevelPflag struct {
value *VerbosityLevel
}
func (wrapper verbosityLevelPflag) String() string {
return strconv.FormatInt(int64(*wrapper.value), 10)
}
func (wrapper verbosityLevelPflag) Get() interface{} {
return *wrapper.value
}
func (wrapper verbosityLevelPflag) Set(value string) error {
// Limited to int32 for compatibility with klog.
v, err := strconv.ParseUint(value, 10, 31)
if err != nil {
return err
}
*wrapper.value = VerbosityLevel(v)
return nil
}
func (wrapper verbosityLevelPflag) Type() string {
return "Level"
}

View File

@ -14,22 +14,19 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package registry
package v1
import (
"fmt"
"sort"
"github.com/go-logr/logr"
"k8s.io/component-base/config"
)
// LogRegistry is new init LogFormatRegistry struct
var LogRegistry = NewLogFormatRegistry()
var logRegistry = newLogFormatRegistry()
// LogFormatRegistry store klog format registry
type LogFormatRegistry struct {
// logFormatRegistry stores factories for all supported logging formats.
type logFormatRegistry struct {
registry map[string]LogFormatFactory
frozen bool
}
@ -41,22 +38,28 @@ type LogFormatFactory interface {
// Returning a flush function for the logger is optional.
// If provided, the caller must ensure that it is called
// periodically (if desired) and at program exit.
Create(c config.LoggingConfiguration) (log logr.Logger, flush func())
Create(c LoggingConfiguration) (log logr.Logger, flush func())
}
// NewLogFormatRegistry return new init LogFormatRegistry struct
func NewLogFormatRegistry() *LogFormatRegistry {
return &LogFormatRegistry{
// RegisterLogFormat registers support for a new logging format. This must be called
// before using any of the methods in LoggingConfiguration.
func RegisterLogFormat(name string, factory LogFormatFactory) error {
return logRegistry.register(name, factory)
}
func newLogFormatRegistry() *logFormatRegistry {
registry := &logFormatRegistry{
registry: make(map[string]LogFormatFactory),
frozen: false,
}
registry.register("text", nil)
return registry
}
// Register new log format registry to global logRegistry.
// nil is valid and selects the default klog output.
func (lfr *LogFormatRegistry) Register(name string, factory LogFormatFactory) error {
// register adds a new log format. It's an error to modify an existing one.
func (lfr *logFormatRegistry) register(name string, factory LogFormatFactory) error {
if lfr.frozen {
return fmt.Errorf("log format is frozen, unable to register log format")
return fmt.Errorf("log format registry is frozen, unable to register log format %s", name)
}
if _, ok := lfr.registry[name]; ok {
return fmt.Errorf("log format: %s already exists", name)
@ -65,8 +68,8 @@ func (lfr *LogFormatRegistry) Register(name string, factory LogFormatFactory) er
return nil
}
// Get specified log format logger
func (lfr *LogFormatRegistry) Get(name string) (LogFormatFactory, error) {
// get specified log format factory
func (lfr *logFormatRegistry) get(name string) (LogFormatFactory, error) {
re, ok := lfr.registry[name]
if !ok {
return nil, fmt.Errorf("log format: %s does not exists", name)
@ -74,28 +77,8 @@ func (lfr *LogFormatRegistry) Get(name string) (LogFormatFactory, error) {
return re, nil
}
// Set specified log format logger
func (lfr *LogFormatRegistry) Set(name string, factory LogFormatFactory) error {
if lfr.frozen {
return fmt.Errorf("log format is frozen, unable to set log format")
}
lfr.registry[name] = factory
return nil
}
// Delete specified log format logger
func (lfr *LogFormatRegistry) Delete(name string) error {
if lfr.frozen {
return fmt.Errorf("log format is frozen, unable to delete log format")
}
delete(lfr.registry, name)
return nil
}
// List names of registered log formats (sorted)
func (lfr *LogFormatRegistry) List() []string {
// list names of registered log formats (sorted)
func (lfr *logFormatRegistry) list() []string {
formats := make([]string, 0, len(lfr.registry))
for f := range lfr.registry {
formats = append(formats, f)
@ -104,7 +87,7 @@ func (lfr *LogFormatRegistry) List() []string {
return formats
}
// Freeze freezes the log format registry
func (lfr *LogFormatRegistry) Freeze() {
// freeze prevents further modifications of the registered log formats.
func (lfr *logFormatRegistry) freeze() {
lfr.frozen = true
}

View File

@ -0,0 +1,92 @@
/*
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 v1
import (
"time"
"k8s.io/apimachinery/pkg/api/resource"
)
// Supported output formats.
const (
// DefaultLogFormat is the traditional klog output format.
DefaultLogFormat = "text"
// JSONLogFormat emits each log message as a JSON struct.
JSONLogFormat = "json"
)
// LoggingConfiguration contains logging options.
type LoggingConfiguration struct {
// Format Flag specifies the structure of log messages.
// default value of format is `text`
Format string `json:"format,omitempty"`
// Maximum number of nanoseconds (i.e. 1s = 1000000000) 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 VerbosityLevel `json:"verbosity"`
// VModule overrides the verbosity threshold for individual files.
// Only supported for "text" log format.
VModule VModuleConfiguration `json:"vmodule,omitempty"`
// [Experimental] Options holds additional parameters that are specific
// to the different logging formats. Only the options for the selected
// format get used, but all of them get validated.
Options FormatOptions `json:"options,omitempty"`
}
// FormatOptions contains options for the different logging formats.
type FormatOptions struct {
// [Experimental] JSON contains options for logging format "json".
JSON JSONOptions `json:"json,omitempty"`
}
// JSONOptions contains options for logging format "json".
type JSONOptions struct {
// [Experimental] SplitStream redirects error messages to stderr while
// info messages go to stdout, with buffering. The default is to write
// both to stdout, without buffering.
SplitStream bool `json:"splitStream,omitempty"`
// [Experimental] InfoBufferSize sets the size of the info stream when
// using split streams. The default is zero, which disables buffering.
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 VerbosityLevel `json:"verbosity"`
}
// VerbosityLevel represents a klog or logr verbosity threshold.
type VerbosityLevel uint32

View File

@ -0,0 +1,266 @@
/*
Copyright 2022 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 v1
import (
"fmt"
"math"
"reflect"
"testing"
"time"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/api/resource"
"sigs.k8s.io/json"
)
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
value := VModuleConfigurationPflag(&actual)
err := value.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 := value.String()
expectParam := test.expectParam
if expectParam == "" {
expectParam = test.arg
}
assert.Equal(t, expectParam, param, "encoded parameter value not identical")
}
})
}
}
// TestCompatibility ensures that a) valid JSON remains valid and has the same
// effect and b) that new fields are covered by the test data.
func TestCompatibility(t *testing.T) {
testcases := map[string]struct {
// fixture holds a representation of a LoggingConfiguration struct in JSON format.
fixture string
// baseConfig is the struct that Unmarshal writes into.
baseConfig LoggingConfiguration
// expectAllFields enables a reflection check to ensure that the
// result has all fields set.
expectAllFields bool
// expectConfig is the intended result.
expectConfig LoggingConfiguration
}{
"defaults": {
// No changes when nothing is specified.
fixture: "{}",
baseConfig: *NewLoggingConfiguration(),
expectConfig: *NewLoggingConfiguration(),
},
"all-fields": {
// The JSON fixture includes all fields. The result
// must have all fields as non-empty when starting with
// an empty base, otherwise the fixture is incomplete
// and must be updated for the test case to pass.
fixture: `{
"format": "json",
"flushFrequency": 1,
"verbosity": 5,
"vmodule": [
{"filePattern": "someFile", "verbosity": 10},
{"filePattern": "anotherFile", "verbosity": 1}
],
"options": {
"json": {
"splitStream": true,
"infoBufferSize": "1024"
}
}
}
`,
baseConfig: LoggingConfiguration{},
expectAllFields: true,
expectConfig: LoggingConfiguration{
Format: JSONLogFormat,
FlushFrequency: time.Nanosecond,
Verbosity: VerbosityLevel(5),
VModule: VModuleConfiguration{
{
FilePattern: "someFile",
Verbosity: VerbosityLevel(10),
},
{
FilePattern: "anotherFile",
Verbosity: VerbosityLevel(1),
},
},
Options: FormatOptions{
JSON: JSONOptions{
SplitStream: true,
InfoBufferSize: resource.QuantityValue{
Quantity: *resource.NewQuantity(1024, resource.DecimalSI),
},
},
},
},
},
}
for name, tc := range testcases {
t.Run(name, func(t *testing.T) {
// Beware, not a deep copy. Different test cases must
// not share anything.
config := tc.baseConfig
if strictErr, err := json.UnmarshalStrict([]byte(tc.fixture), &config); err != nil {
t.Fatalf("unexpected unmarshal error: %v", err)
} else if strictErr != nil {
t.Fatalf("unexpected strict unmarshal error: %v", strictErr)
}
// This sets the internal "s" field just like unmarshaling does.
// Required for assert.Equal to pass.
_ = tc.expectConfig.Options.JSON.InfoBufferSize.String()
assert.Equal(t, tc.expectConfig, config)
if tc.expectAllFields {
notZeroRecursive(t, config, "LoggingConfiguration")
}
})
}
}
// notZero asserts that i is not the zero value for its type
// and repeats that check recursively for all pointers,
// structs, maps, arrays, and slices.
func notZeroRecursive(t *testing.T, i interface{}, path string) bool {
typeOfI := reflect.TypeOf(i)
if i == nil || reflect.DeepEqual(i, reflect.Zero(typeOfI).Interface()) {
t.Errorf("%s: should not have been zero, but was %v", path, i)
return false
}
valid := true
kind := typeOfI.Kind()
value := reflect.ValueOf(i)
switch kind {
case reflect.Ptr:
if !notZeroRecursive(t, value.Elem().Interface(), path) {
valid = false
}
case reflect.Struct:
for i := 0; i < typeOfI.NumField(); i++ {
if !typeOfI.Field(i).IsExported() {
// Cannot access value.
continue
}
if !notZeroRecursive(t, value.Field(i).Interface(), path+"."+typeOfI.Field(i).Name) {
valid = false
}
}
case reflect.Map:
iter := value.MapRange()
for iter.Next() {
k := iter.Key()
v := iter.Value()
if !notZeroRecursive(t, k.Interface(), path+"."+"<key>") {
valid = false
}
if !notZeroRecursive(t, v.Interface(), path+"["+fmt.Sprintf("%v", k.Interface())+"]") {
valid = false
}
}
case reflect.Slice, reflect.Array:
for i := 0; i < value.Len(); i++ {
if !notZeroRecursive(t, value.Index(i).Interface(), path+"["+fmt.Sprintf("%d", i)+"]") {
valid = false
}
}
}
return valid
}

View File

@ -1,70 +0,0 @@
/*
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 (
"fmt"
"math"
"strings"
"k8s.io/apimachinery/pkg/util/validation/field"
cliflag "k8s.io/component-base/cli/flag"
"k8s.io/component-base/config"
"k8s.io/component-base/logs/registry"
)
func ValidateLoggingConfiguration(c *config.LoggingConfiguration, fldPath *field.Path) field.ErrorList {
errs := field.ErrorList{}
if c.Format != DefaultLogFormat {
// WordSepNormalizeFunc is just a guess. Commands should use it,
// but we cannot know for sure.
allFlags := UnsupportedLoggingFlags(cliflag.WordSepNormalizeFunc)
for _, f := range allFlags {
if f.DefValue != f.Value.String() {
errs = append(errs, field.Invalid(fldPath.Child("format"), c.Format, fmt.Sprintf("Non-default format doesn't honor flag: %s", f.Name)))
}
}
}
_, err := registry.LogRegistry.Get(c.Format)
if err != nil {
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.
return errs
}

View File

@ -14,27 +14,27 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package logs
package v1
import (
"math"
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/component-base/config"
"k8s.io/apimachinery/pkg/util/validation/field"
)
func TestValidateLoggingConfiguration(t *testing.T) {
func TestValidation(t *testing.T) {
testcases := map[string]struct {
config config.LoggingConfiguration
config LoggingConfiguration
path *field.Path
expectErrors string
}{
"okay": {
config: config.LoggingConfiguration{
config: LoggingConfiguration{
Format: "text",
Verbosity: 10,
VModule: config.VModuleConfiguration{
VModule: VModuleConfiguration{
{
FilePattern: "gopher*",
Verbosity: 100,
@ -43,22 +43,29 @@ func TestValidateLoggingConfiguration(t *testing.T) {
},
},
"wrong-format": {
config: config.LoggingConfiguration{
config: LoggingConfiguration{
Format: "no-such-format",
},
expectErrors: `format: Invalid value: "no-such-format": Unsupported log format`,
},
"embedded": {
config: LoggingConfiguration{
Format: "no-such-format",
},
path: field.NewPath("config"),
expectErrors: `config.format: Invalid value: "no-such-format": Unsupported log format`,
},
"verbosity-overflow": {
config: config.LoggingConfiguration{
config: LoggingConfiguration{
Format: "text",
Verbosity: math.MaxInt32 + 1,
},
expectErrors: `verbosity: Invalid value: 0x80000000: Must be <= 2147483647`,
},
"vmodule-verbosity-overflow": {
config: config.LoggingConfiguration{
config: LoggingConfiguration{
Format: "text",
VModule: config.VModuleConfiguration{
VModule: VModuleConfiguration{
{
FilePattern: "gopher*",
Verbosity: math.MaxInt32 + 1,
@ -68,9 +75,9 @@ func TestValidateLoggingConfiguration(t *testing.T) {
expectErrors: `vmodule[0]: Invalid value: 0x80000000: Must be <= 2147483647`,
},
"vmodule-empty-pattern": {
config: config.LoggingConfiguration{
config: LoggingConfiguration{
Format: "text",
VModule: config.VModuleConfiguration{
VModule: VModuleConfiguration{
{
FilePattern: "",
Verbosity: 1,
@ -80,9 +87,9 @@ func TestValidateLoggingConfiguration(t *testing.T) {
expectErrors: `vmodule[0]: Required value: File pattern must not be empty`,
},
"vmodule-pattern-with-special-characters": {
config: config.LoggingConfiguration{
config: LoggingConfiguration{
Format: "text",
VModule: config.VModuleConfiguration{
VModule: VModuleConfiguration{
{
FilePattern: "foo,bar",
Verbosity: 1,
@ -96,9 +103,9 @@ func TestValidateLoggingConfiguration(t *testing.T) {
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{
config: LoggingConfiguration{
Format: "json",
VModule: config.VModuleConfiguration{
VModule: VModuleConfiguration{
{
FilePattern: "foo",
Verbosity: 1,
@ -111,13 +118,13 @@ func TestValidateLoggingConfiguration(t *testing.T) {
for name, test := range testcases {
t.Run(name, func(t *testing.T) {
errs := ValidateLoggingConfiguration(&test.config, nil)
if len(errs) == 0 {
err := test.config.Validate(nil, test.path)
if len(err) == 0 {
if test.expectErrors != "" {
t.Fatalf("did not get expected error(s): %s", test.expectErrors)
}
} else {
assert.Equal(t, test.expectErrors, errs.ToAggregate().Error())
assert.Equal(t, test.expectErrors, err.ToAggregate().Error())
}
})
}

View File

@ -0,0 +1,114 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FormatOptions) DeepCopyInto(out *FormatOptions) {
*out = *in
in.JSON.DeepCopyInto(&out.JSON)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FormatOptions.
func (in *FormatOptions) DeepCopy() *FormatOptions {
if in == nil {
return nil
}
out := new(FormatOptions)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JSONOptions) DeepCopyInto(out *JSONOptions) {
*out = *in
in.InfoBufferSize.DeepCopyInto(&out.InfoBufferSize)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONOptions.
func (in *JSONOptions) DeepCopy() *JSONOptions {
if in == nil {
return nil
}
out := new(JSONOptions)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LoggingConfiguration) DeepCopyInto(out *LoggingConfiguration) {
*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)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoggingConfiguration.
func (in *LoggingConfiguration) DeepCopy() *LoggingConfiguration {
if in == nil {
return nil
}
out := new(LoggingConfiguration)
in.DeepCopyInto(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

@ -1,116 +0,0 @@
/*
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 (
"flag"
"fmt"
"sort"
"strings"
"github.com/spf13/pflag"
cliflag "k8s.io/component-base/cli/flag"
"k8s.io/component-base/config"
"k8s.io/component-base/logs/registry"
"k8s.io/klog/v2"
)
// Supported klog formats
const (
DefaultLogFormat = "text"
JSONLogFormat = "json"
)
// loggingFlags captures the state of the logging flags, in particular their default value
// before flag parsing. It is used by UnsupportedLoggingFlags.
var loggingFlags pflag.FlagSet
func init() {
// Text format is default klog format
registry.LogRegistry.Register(DefaultLogFormat, nil)
var fs flag.FlagSet
klog.InitFlags(&fs)
loggingFlags.AddGoFlagSet(&fs)
}
// List of logs (k8s.io/klog + k8s.io/component-base/logs) flags supported by all logging formats
var supportedLogsFlags = map[string]struct{}{
"v": {},
}
// 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) {
// The help text is generated assuming that flags will eventually use
// hyphens, even if currently no normalization function is set for the
// flag set yet.
unsupportedFlags := strings.Join(unsupportedLoggingFlagNames(cliflag.WordSepNormalizeFunc), ", ")
formats := fmt.Sprintf(`"%s"`, strings.Join(registry.LogRegistry.List(), `", "`))
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
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)")
// JSON options. We only register them if "json" is a valid format. The
// config file API however always has them.
if _, err := registry.LogRegistry.Get("json"); err == nil {
fs.BoolVar(&c.Options.JSON.SplitStream, "log-json-split-stream", false, "[Experimental] In JSON format, write error messages to stderr and info messages to stdout. The default is to write a single stream to stdout.")
fs.Var(&c.Options.JSON.InfoBufferSize, "log-json-info-buffer-size", "[Experimental] In JSON format with split output streams, the info messages can be buffered for a while to increase performance. The default value of zero bytes disables buffering. The size can be specified as number of bytes (512), multiples of 1000 (1K), multiples of 1024 (2Ki), or powers of those (3M, 4G, 5Mi, 6Gi).")
}
}
// UnsupportedLoggingFlags lists unsupported logging flags. The normalize
// function is optional.
func UnsupportedLoggingFlags(normalizeFunc func(f *pflag.FlagSet, name string) pflag.NormalizedName) []*pflag.Flag {
// k8s.io/component-base/logs and klog flags
pfs := &pflag.FlagSet{}
loggingFlags.VisitAll(func(flag *pflag.Flag) {
if _, found := supportedLogsFlags[flag.Name]; !found {
// Normalization changes flag.Name, so make a copy.
clone := *flag
pfs.AddFlag(&clone)
}
})
// Apply normalization.
pfs.SetNormalizeFunc(normalizeFunc)
var allFlags []*pflag.Flag
pfs.VisitAll(func(flag *pflag.Flag) {
allFlags = append(allFlags, flag)
})
return allFlags
}
// unsupportedLoggingFlagNames lists unsupported logging flags by name, with
// optional normalization and sorted.
func unsupportedLoggingFlagNames(normalizeFunc func(f *pflag.FlagSet, name string) pflag.NormalizedName) []string {
unsupportedFlags := UnsupportedLoggingFlags(normalizeFunc)
names := make([]string, 0, len(unsupportedFlags))
for _, f := range unsupportedFlags {
names = append(names, "--"+f.Name)
}
sort.Strings(names)
return names
}

View File

@ -29,6 +29,7 @@ import (
"k8s.io/component-base/cli"
"k8s.io/component-base/featuregate"
"k8s.io/component-base/logs"
logsapi "k8s.io/component-base/logs/api/v1"
"k8s.io/klog/v2"
_ "k8s.io/component-base/logs/json/register"
@ -37,7 +38,7 @@ import (
var featureGate = featuregate.NewFeatureGate()
func main() {
runtime.Must(logs.AddFeatureGates(featureGate))
runtime.Must(logsapi.AddFeatureGates(featureGate))
command := NewLoggerCommand()
// Intentionally broken: logging is not initialized yet.
@ -48,11 +49,11 @@ func main() {
}
func NewLoggerCommand() *cobra.Command {
o := logs.NewOptions()
c := logsapi.NewLoggingConfiguration()
cmd := &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
logs.InitLogs()
if err := o.ValidateAndApply(featureGate); err != nil {
if err := c.ValidateAndApply(featureGate); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
@ -64,9 +65,9 @@ func NewLoggerCommand() *cobra.Command {
runLogger(ctx)
},
}
logs.AddFeatureGates(featureGate)
logsapi.AddFeatureGates(featureGate)
featureGate.AddFlag(cmd.Flags())
o.AddFlags(cmd.Flags())
c.AddFlags(cmd.Flags())
return cmd
}

View File

@ -26,8 +26,7 @@ import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"k8s.io/component-base/config"
"k8s.io/component-base/logs/registry"
logsapi "k8s.io/component-base/logs/api/v1"
)
var (
@ -38,7 +37,7 @@ var (
// NewJSONLogger creates a new json logr.Logger and its associated
// flush function. The separate error stream is optional and may be nil.
// The encoder config is also optional.
func NewJSONLogger(v config.VerbosityLevel, infoStream, errorStream zapcore.WriteSyncer, encoderConfig *zapcore.EncoderConfig) (logr.Logger, func()) {
func NewJSONLogger(v logsapi.VerbosityLevel, infoStream, errorStream zapcore.WriteSyncer, encoderConfig *zapcore.EncoderConfig) (logr.Logger, func()) {
// zap levels are inverted: everything with a verbosity >= threshold gets logged.
zapV := -zapcore.Level(v)
@ -85,9 +84,9 @@ func epochMillisTimeEncoder(_ time.Time, enc zapcore.PrimitiveArrayEncoder) {
// Factory produces JSON logger instances.
type Factory struct{}
var _ registry.LogFormatFactory = Factory{}
var _ logsapi.LogFormatFactory = Factory{}
func (f Factory) Create(c config.LoggingConfiguration) (logr.Logger, func()) {
func (f Factory) Create(c logsapi.LoggingConfiguration) (logr.Logger, func()) {
// 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.

View File

@ -24,10 +24,10 @@ import (
"time"
"github.com/stretchr/testify/assert"
"k8s.io/component-base/config"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
logsapi "k8s.io/component-base/logs/api/v1"
)
// TestZapLoggerInfo test ZapLogger json info format
@ -112,7 +112,7 @@ func TestZapLoggerInfo(t *testing.T) {
// TestZapLoggerEnabled test ZapLogger enabled
func TestZapLoggerEnabled(t *testing.T) {
verbosityLevel := 10
sampleInfoLogger, _ := NewJSONLogger(config.VerbosityLevel(verbosityLevel), nil, nil, nil)
sampleInfoLogger, _ := NewJSONLogger(logsapi.VerbosityLevel(verbosityLevel), nil, nil, nil)
for v := 0; v <= verbosityLevel; v++ {
enabled := sampleInfoLogger.V(v).Enabled()
expectEnabled := v <= verbosityLevel
@ -135,7 +135,7 @@ func TestZapLoggerV(t *testing.T) {
for v := 0; v <= verbosityLevel; v++ {
var buffer bytes.Buffer
writer := zapcore.AddSync(&buffer)
sampleInfoLogger, _ := NewJSONLogger(config.VerbosityLevel(verbosityLevel), writer, nil, nil)
sampleInfoLogger, _ := NewJSONLogger(logsapi.VerbosityLevel(verbosityLevel), writer, nil, nil)
sampleInfoLogger.V(v).Info("test", "ns", "default", "podnum", 2, "time", time.Microsecond)
logStr := buffer.String()

View File

@ -28,7 +28,7 @@ import (
"github.com/stretchr/testify/assert"
"go.uber.org/zap/zapcore"
"k8s.io/component-base/config"
logsapi "k8s.io/component-base/logs/api/v1"
"k8s.io/klog/v2"
)
@ -241,7 +241,7 @@ func TestKlogIntegration(t *testing.T) {
var buffer bytes.Buffer
writer := zapcore.AddSync(&buffer)
// This level is high enough to enable all log messages from this test.
verbosityLevel := config.VerbosityLevel(100)
verbosityLevel := logsapi.VerbosityLevel(100)
logger, _ := NewJSONLogger(verbosityLevel, writer, nil, nil)
klog.SetLogger(logger)
defer klog.ClearLogger()

View File

@ -17,12 +17,13 @@ limitations under the License.
package register
import (
"k8s.io/component-base/logs"
logsapi "k8s.io/component-base/logs/api/v1"
json "k8s.io/component-base/logs/json"
"k8s.io/component-base/logs/registry"
)
func init() {
// JSON format is optional klog format
registry.LogRegistry.Register(logs.JSONLogFormat, json.Factory{})
if err := logsapi.RegisterLogFormat(logsapi.JSONLogFormat, json.Factory{}); err != nil {
panic(err)
}
}

View File

@ -26,15 +26,15 @@ import (
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/component-base/featuregate"
"k8s.io/component-base/logs"
logsapi "k8s.io/component-base/logs/api/v1"
"k8s.io/klog/v2"
)
func TestJSONFlag(t *testing.T) {
o := logs.NewOptions()
c := logsapi.NewLoggingConfiguration()
fs := pflag.NewFlagSet("addflagstest", pflag.ContinueOnError)
output := bytes.Buffer{}
o.AddFlags(fs)
c.AddFlags(fs)
fs.SetOutput(&output)
fs.PrintDefaults()
wantSubstring := `Permitted formats: "json", "text".`
@ -44,41 +44,41 @@ func TestJSONFlag(t *testing.T) {
}
func TestJSONFormatRegister(t *testing.T) {
newOptions := logs.NewOptions()
config := logsapi.NewLoggingConfiguration()
klogr := klog.Background()
testcases := []struct {
name string
args []string
contextualLogging bool
want *logs.Options
want *logsapi.LoggingConfiguration
errs field.ErrorList
}{
{
name: "JSON log format",
args: []string{"--logging-format=json"},
want: func() *logs.Options {
c := newOptions.Config.DeepCopy()
c.Format = logs.JSONLogFormat
return &logs.Options{*c}
want: func() *logsapi.LoggingConfiguration {
c := config.DeepCopy()
c.Format = logsapi.JSONLogFormat
return c
}(),
},
{
name: "JSON direct",
args: []string{"--logging-format=json"},
contextualLogging: true,
want: func() *logs.Options {
c := newOptions.Config.DeepCopy()
c.Format = logs.JSONLogFormat
return &logs.Options{*c}
want: func() *logsapi.LoggingConfiguration {
c := config.DeepCopy()
c.Format = logsapi.JSONLogFormat
return c
}(),
},
{
name: "Unsupported log format",
args: []string{"--logging-format=test"},
want: func() *logs.Options {
c := newOptions.Config.DeepCopy()
want: func() *logsapi.LoggingConfiguration {
c := config.DeepCopy()
c.Format = "test"
return &logs.Options{*c}
return c
}(),
errs: field.ErrorList{&field.Error{
Type: "FieldValueInvalid",
@ -91,18 +91,18 @@ func TestJSONFormatRegister(t *testing.T) {
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
o := logs.NewOptions()
c := logsapi.NewLoggingConfiguration()
fs := pflag.NewFlagSet("addflagstest", pflag.ContinueOnError)
o.AddFlags(fs)
c.AddFlags(fs)
fs.Parse(tc.args)
if !assert.Equal(t, tc.want, o) {
t.Errorf("Wrong Validate() result for %q. expect %v, got %v", tc.name, tc.want, o)
if !assert.Equal(t, tc.want, c) {
t.Errorf("Wrong Validate() result for %q. expect %v, got %v", tc.name, tc.want, c)
}
featureGate := featuregate.NewFeatureGate()
logs.AddFeatureGates(featureGate)
err := featureGate.SetFromMap(map[string]bool{string(logs.ContextualLogging): tc.contextualLogging})
logsapi.AddFeatureGates(featureGate)
err := featureGate.SetFromMap(map[string]bool{string(logsapi.ContextualLogging): tc.contextualLogging})
require.NoError(t, err)
errs := o.ValidateAndApply(featureGate)
errs := c.ValidateAndApply(featureGate)
defer klog.ClearLogger()
if !assert.ElementsMatch(t, tc.errs, errs) {
t.Errorf("Wrong Validate() result for %q.\n expect:\t%+v\n got:\t%+v", tc.name, tc.errs, errs)

View File

@ -26,10 +26,10 @@ import (
"time"
"github.com/spf13/pflag"
logsapi "k8s.io/component-base/logs/api/v1"
"k8s.io/klog/v2"
)
const logFlushFreqFlagName = "log-flush-frequency"
const deprecated = "will be removed in a future release, see https://github.com/kubernetes/enhancements/tree/master/keps/sig-instrumentation/2845-deprecate-klog-specific-flags-in-k8s-components"
// TODO (https://github.com/kubernetes/kubernetes/issues/105310): once klog
@ -48,7 +48,7 @@ var (
func init() {
klog.InitFlags(packageFlags)
packageFlags.DurationVar(&logFlushFreq, logFlushFreqFlagName, 5*time.Second, "Maximum number of seconds between log flushes")
packageFlags.DurationVar(&logFlushFreq, logsapi.LogFlushFreqFlagName, logsapi.LogFlushFreqDefault, "Maximum number of seconds between log flushes")
}
type addFlagsOptions struct {
@ -66,6 +66,13 @@ func SkipLoggingConfigurationFlags() Option {
}
}
// Options is an alias for LoggingConfiguration to comply with component-base
// conventions.
type Options = logsapi.LoggingConfiguration
// NewOptions is an alias for NewLoggingConfiguration.
var NewOptions = logsapi.NewLoggingConfiguration
// AddFlags registers this package's flags on arbitrary FlagSets. This includes
// the klog flags, with the original underscore as separator between. If
// commands want hyphens as separators, they can set
@ -94,7 +101,7 @@ func AddFlags(fs *pflag.FlagSet, opts ...Option) {
if o.skipLoggingConfigurationFlags {
return
}
case logFlushFreqFlagName:
case logsapi.LogFlushFreqFlagName:
// unchanged, potentially skip it
if o.skipLoggingConfigurationFlags {
return
@ -135,7 +142,7 @@ func AddGoFlags(fs *flag.FlagSet, opts ...Option) {
if o.skipLoggingConfigurationFlags {
return
}
case logFlushFreqFlagName:
case logsapi.LogFlushFreqFlagName:
// unchanged
if o.skipLoggingConfigurationFlags {
return

View File

@ -17,7 +17,6 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect

View File

@ -28,7 +28,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=

View File

@ -17,7 +17,6 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect

View File

@ -29,7 +29,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=

View File

@ -19,7 +19,6 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect

View File

@ -29,7 +29,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=

View File

@ -19,7 +19,7 @@ package v1beta1
import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
componentbaseconfigv1alpha1 "k8s.io/component-base/config/v1alpha1"
logsapi "k8s.io/component-base/logs/api/v1"
)
// HairpinMode denotes how the kubelet should configure networking to handle
@ -676,7 +676,7 @@ type KubeletConfiguration struct {
// Default:
// Format: text
// + optional
Logging componentbaseconfigv1alpha1.LoggingConfiguration `json:"logging,omitempty"`
Logging logsapi.LoggingConfiguration `json:"logging,omitempty"`
// enableSystemLogHandler enables system logs via web interface host:port/logs/
// Default: true
// +optional

View File

@ -18,9 +18,11 @@ require (
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/google/gofuzz v1.1.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/spf13/cobra v1.4.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
golang.org/x/text v0.3.7 // indirect

View File

@ -12,6 +12,7 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -58,6 +59,8 @@ github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
@ -75,7 +78,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

View File

@ -33,7 +33,7 @@ import (
"time"
"github.com/go-logr/logr"
"k8s.io/component-base/logs"
logsapi "k8s.io/component-base/logs/api/v1"
logsjson "k8s.io/component-base/logs/json"
"k8s.io/klog/v2"
)
@ -175,7 +175,7 @@ func benchmarkOutputFormats(b *testing.B, config loadGeneratorConfig, discard bo
generateOutput(b, config, nil, out)
})
b.Run("JSON", func(b *testing.B) {
options := logs.NewOptions()
c := logsapi.NewLoggingConfiguration()
var logger logr.Logger
var flush func()
var out1, out2 *os.File
@ -194,14 +194,14 @@ func benchmarkOutputFormats(b *testing.B, config loadGeneratorConfig, discard bo
}
b.Run("single-stream", func(b *testing.B) {
if discard {
logger, flush = logsjson.NewJSONLogger(options.Config.Verbosity, logsjson.AddNopSync(&output), nil, nil)
logger, flush = logsjson.NewJSONLogger(c.Verbosity, logsjson.AddNopSync(&output), nil, nil)
} else {
stderr := os.Stderr
os.Stderr = out1
defer func() {
os.Stderr = stderr
}()
logger, flush = logsjson.Factory{}.Create(options.Config)
logger, flush = logsjson.Factory{}.Create(*c)
}
klog.SetLogger(logger)
defer klog.ClearLogger()
@ -210,16 +210,16 @@ func benchmarkOutputFormats(b *testing.B, config loadGeneratorConfig, discard bo
b.Run("split-stream", func(b *testing.B) {
if discard {
logger, flush = logsjson.NewJSONLogger(options.Config.Verbosity, logsjson.AddNopSync(&output), logsjson.AddNopSync(&output), nil)
logger, flush = logsjson.NewJSONLogger(c.Verbosity, logsjson.AddNopSync(&output), logsjson.AddNopSync(&output), nil)
} else {
stdout, stderr := os.Stdout, os.Stderr
os.Stdout, os.Stderr = out1, out2
defer func() {
os.Stdout, os.Stderr = stdout, stderr
}()
options := logs.NewOptions()
options.Config.Options.JSON.SplitStream = true
logger, flush = logsjson.Factory{}.Create(options.Config)
c := logsapi.NewLoggingConfiguration()
c.Options.JSON.SplitStream = true
logger, flush = logsjson.Factory{}.Create(*c)
}
klog.SetLogger(logger)
defer klog.ClearLogger()

View File

@ -23,7 +23,7 @@ import (
"github.com/go-logr/logr"
"go.uber.org/zap/zapcore"
"k8s.io/component-base/logs"
logsapi "k8s.io/component-base/logs/api/v1"
logsjson "k8s.io/component-base/logs/json"
"k8s.io/klog/v2"
)
@ -61,8 +61,8 @@ func newJSONLogger(out io.Writer) logr.Logger {
encoderConfig := &zapcore.EncoderConfig{
MessageKey: "msg",
}
options := logs.NewOptions()
logger, _ := logsjson.NewJSONLogger(options.Config.Verbosity, zapcore.AddSync(out), nil, encoderConfig)
c := logsapi.NewLoggingConfiguration()
logger, _ := logsjson.NewJSONLogger(c.Verbosity, zapcore.AddSync(out), nil, encoderConfig)
return logger
}

View File

@ -25,7 +25,7 @@ import (
"github.com/go-logr/logr"
"go.uber.org/zap/zapcore"
"k8s.io/component-base/config"
logsapi "k8s.io/component-base/logs/api/v1"
logsjson "k8s.io/component-base/logs/json"
"k8s.io/klog/v2/test"
)
@ -37,7 +37,7 @@ func init() {
// TestJsonOutput tests the JSON logger, directly and as backend for klog.
func TestJSONOutput(t *testing.T) {
newLogger := func(out io.Writer, v int, vmodule string) logr.Logger {
logger, _ := logsjson.NewJSONLogger(config.VerbosityLevel(v), logsjson.AddNopSync(out), nil,
logger, _ := logsjson.NewJSONLogger(logsapi.VerbosityLevel(v), logsjson.AddNopSync(out), nil,
&zapcore.EncoderConfig{
MessageKey: "msg",
CallerKey: "caller",

2
vendor/modules.txt vendored
View File

@ -2063,10 +2063,10 @@ k8s.io/component-base/configz
k8s.io/component-base/featuregate
k8s.io/component-base/featuregate/testing
k8s.io/component-base/logs
k8s.io/component-base/logs/api/v1
k8s.io/component-base/logs/json
k8s.io/component-base/logs/json/register
k8s.io/component-base/logs/logreduction
k8s.io/component-base/logs/registry
k8s.io/component-base/logs/testinit
k8s.io/component-base/metrics
k8s.io/component-base/metrics/legacyregistry