1
16
17 package v1
18
19 import (
20 "errors"
21 "flag"
22 "fmt"
23 "io"
24 "math"
25 "os"
26 "strings"
27 "sync/atomic"
28 "time"
29
30 "github.com/google/go-cmp/cmp"
31 "github.com/spf13/pflag"
32
33 "k8s.io/klog/v2"
34 "k8s.io/klog/v2/textlogger"
35
36 "k8s.io/apimachinery/pkg/api/resource"
37 "k8s.io/apimachinery/pkg/util/validation/field"
38 cliflag "k8s.io/component-base/cli/flag"
39 "k8s.io/component-base/featuregate"
40 "k8s.io/component-base/logs/internal/setverbositylevel"
41 "k8s.io/component-base/logs/klogflags"
42 )
43
44 const (
45
46
47 LogFlushFreqDefault = 5 * time.Second
48 )
49
50 const (
51
52
53
54 LogFlushFreqFlagName = "log-flush-frequency"
55 )
56
57
58 func NewLoggingConfiguration() *LoggingConfiguration {
59 c := LoggingConfiguration{}
60 SetRecommendedLoggingConfiguration(&c)
61 return &c
62 }
63
64
65
66
67
68 var ReapplyHandling = ReapplyHandlingError
69
70 type ReapplyHandlingType int
71
72 const (
73
74
75 ReapplyHandlingError ReapplyHandlingType = iota
76
77
78
79 ReapplyHandlingIgnoreUnchanged
80 )
81
82
83
84
85
86
87
88
89
90
91
92
93 func ValidateAndApply(c *LoggingConfiguration, featureGate featuregate.FeatureGate) error {
94 return validateAndApply(c, nil, featureGate, nil)
95 }
96
97
98
99
100
101
102
103
104 func ValidateAndApplyWithOptions(c *LoggingConfiguration, options *LoggingOptions, featureGate featuregate.FeatureGate) error {
105 return validateAndApply(c, options, featureGate, nil)
106 }
107
108
109
110
111
112 type LoggingOptions struct {
113
114 ErrorStream io.Writer
115
116
117 InfoStream io.Writer
118 }
119
120
121
122
123 func ValidateAndApplyAsField(c *LoggingConfiguration, featureGate featuregate.FeatureGate, fldPath *field.Path) error {
124 return validateAndApply(c, nil, featureGate, fldPath)
125 }
126
127 func validateAndApply(c *LoggingConfiguration, options *LoggingOptions, featureGate featuregate.FeatureGate, fldPath *field.Path) error {
128 errs := Validate(c, featureGate, fldPath)
129 if len(errs) > 0 {
130 return errs.ToAggregate()
131 }
132 return apply(c, options, featureGate)
133 }
134
135
136
137
138
139 func Validate(c *LoggingConfiguration, featureGate featuregate.FeatureGate, fldPath *field.Path) field.ErrorList {
140 errs := field.ErrorList{}
141 if c.Format != DefaultLogFormat {
142
143
144 allFlags := unsupportedLoggingFlags(cliflag.WordSepNormalizeFunc)
145 for _, f := range allFlags {
146 if f.DefValue != f.Value.String() {
147 errs = append(errs, field.Invalid(fldPath.Child("format"), c.Format, fmt.Sprintf("Non-default format doesn't honor flag: %s", f.Name)))
148 }
149 }
150 }
151 format, err := logRegistry.get(c.Format)
152 if err != nil {
153 errs = append(errs, field.Invalid(fldPath.Child("format"), c.Format, "Unsupported log format"))
154 } else if format != nil {
155 if format.feature != LoggingStableOptions {
156 enabled := featureGates()[format.feature].Default
157 if featureGate != nil {
158 enabled = featureGate.Enabled(format.feature)
159 }
160 if !enabled {
161 errs = append(errs, field.Forbidden(fldPath.Child("format"), fmt.Sprintf("Log format %s is disabled, see %s feature", c.Format, format.feature)))
162 }
163 }
164 }
165
166
167 if c.Verbosity > math.MaxInt32 {
168 errs = append(errs, field.Invalid(fldPath.Child("verbosity"), c.Verbosity, fmt.Sprintf("Must be <= %d", math.MaxInt32)))
169 }
170 vmoduleFldPath := fldPath.Child("vmodule")
171 if len(c.VModule) > 0 && c.Format != "" && c.Format != "text" {
172 errs = append(errs, field.Forbidden(vmoduleFldPath, "Only supported for text log format"))
173 }
174 for i, item := range c.VModule {
175 if item.FilePattern == "" {
176 errs = append(errs, field.Required(vmoduleFldPath.Index(i), "File pattern must not be empty"))
177 }
178 if strings.ContainsAny(item.FilePattern, "=,") {
179 errs = append(errs, field.Invalid(vmoduleFldPath.Index(i), item.FilePattern, "File pattern must not contain equal sign or comma"))
180 }
181 if item.Verbosity > math.MaxInt32 {
182 errs = append(errs, field.Invalid(vmoduleFldPath.Index(i), item.Verbosity, fmt.Sprintf("Must be <= %d", math.MaxInt32)))
183 }
184 }
185
186 errs = append(errs, validateFormatOptions(c, featureGate, fldPath.Child("options"))...)
187 return errs
188 }
189
190 func validateFormatOptions(c *LoggingConfiguration, featureGate featuregate.FeatureGate, fldPath *field.Path) field.ErrorList {
191 errs := field.ErrorList{}
192 errs = append(errs, validateTextOptions(c, featureGate, fldPath.Child("text"))...)
193 errs = append(errs, validateJSONOptions(c, featureGate, fldPath.Child("json"))...)
194 return errs
195 }
196
197 func validateTextOptions(c *LoggingConfiguration, featureGate featuregate.FeatureGate, fldPath *field.Path) field.ErrorList {
198 errs := field.ErrorList{}
199 if gate := LoggingAlphaOptions; c.Options.Text.SplitStream && !featureEnabled(featureGate, gate) {
200 errs = append(errs, field.Forbidden(fldPath.Child("splitStream"), fmt.Sprintf("Feature %s is disabled", gate)))
201 }
202 if gate := LoggingAlphaOptions; c.Options.Text.InfoBufferSize.Value() != 0 && !featureEnabled(featureGate, gate) {
203 errs = append(errs, field.Forbidden(fldPath.Child("infoBufferSize"), fmt.Sprintf("Feature %s is disabled", gate)))
204 }
205 return errs
206 }
207
208 func validateJSONOptions(c *LoggingConfiguration, featureGate featuregate.FeatureGate, fldPath *field.Path) field.ErrorList {
209 errs := field.ErrorList{}
210 if gate := LoggingAlphaOptions; c.Options.JSON.SplitStream && !featureEnabled(featureGate, gate) {
211 errs = append(errs, field.Forbidden(fldPath.Child("splitStream"), fmt.Sprintf("Feature %s is disabled", gate)))
212 }
213 if gate := LoggingAlphaOptions; c.Options.JSON.InfoBufferSize.Value() != 0 && !featureEnabled(featureGate, gate) {
214 errs = append(errs, field.Forbidden(fldPath.Child("infoBufferSize"), fmt.Sprintf("Feature %s is disabled", gate)))
215 }
216 return errs
217 }
218
219 func featureEnabled(featureGate featuregate.FeatureGate, feature featuregate.Feature) bool {
220 enabled := false
221 if featureGate != nil {
222 enabled = featureGate.Enabled(feature)
223 }
224 return enabled
225 }
226
227 func apply(c *LoggingConfiguration, options *LoggingOptions, featureGate featuregate.FeatureGate) error {
228 p := ¶meters{
229 C: c,
230 Options: options,
231 ContextualLoggingEnabled: contextualLoggingDefault,
232 }
233 if featureGate != nil {
234 p.ContextualLoggingEnabled = featureGate.Enabled(ContextualLogging)
235 }
236
237 oldP := applyParameters.Load()
238 if oldP != nil {
239 switch ReapplyHandling {
240 case ReapplyHandlingError:
241 return errors.New("logging configuration was already applied earlier, changing it is not allowed")
242 case ReapplyHandlingIgnoreUnchanged:
243 if diff := cmp.Diff(oldP, p); diff != "" {
244 return fmt.Errorf("the logging configuration should not be changed after setting it once (- old setting, + new setting):\n%s", diff)
245 }
246 return nil
247 default:
248 return fmt.Errorf("invalid value %d for ReapplyHandling", ReapplyHandling)
249 }
250 }
251 applyParameters.Store(p)
252
253
254 format, _ := logRegistry.get(c.Format)
255 if format.factory == nil {
256 klog.ClearLogger()
257 } else {
258 if options == nil {
259 options = &LoggingOptions{
260 ErrorStream: os.Stderr,
261 InfoStream: os.Stdout,
262 }
263 }
264 log, control := format.factory.Create(*c, *options)
265 if control.SetVerbosityLevel != nil {
266 setverbositylevel.Mutex.Lock()
267 defer setverbositylevel.Mutex.Unlock()
268 setverbositylevel.Callbacks = append(setverbositylevel.Callbacks, control.SetVerbosityLevel)
269 }
270 opts := []klog.LoggerOption{
271 klog.ContextualLogger(p.ContextualLoggingEnabled),
272 klog.FlushLogger(control.Flush),
273 }
274 if writer, ok := log.GetSink().(textlogger.KlogBufferWriter); ok {
275 opts = append(opts, klog.WriteKlogBuffer(writer.WriteKlogBuffer))
276 }
277 klog.SetLoggerWithOptions(log, opts...)
278 }
279 if err := loggingFlags.Lookup("v").Value.Set(VerbosityLevelPflag(&c.Verbosity).String()); err != nil {
280 return fmt.Errorf("internal error while setting klog verbosity: %v", err)
281 }
282 if err := loggingFlags.Lookup("vmodule").Value.Set(VModuleConfigurationPflag(&c.VModule).String()); err != nil {
283 return fmt.Errorf("internal error while setting klog vmodule: %v", err)
284 }
285 klog.StartFlushDaemon(c.FlushFrequency.Duration.Duration)
286 klog.EnableContextualLogging(p.ContextualLoggingEnabled)
287 return nil
288 }
289
290 type parameters struct {
291 C *LoggingConfiguration
292 Options *LoggingOptions
293 ContextualLoggingEnabled bool
294 }
295
296 var applyParameters atomic.Pointer[parameters]
297
298
299
300
301 func ResetForTest(featureGate featuregate.FeatureGate) error {
302 oldP := applyParameters.Load()
303 if oldP == nil {
304
305 return nil
306 }
307
308
309 applyParameters.Store(nil)
310
311
312 config := NewLoggingConfiguration()
313 if err := ValidateAndApply(config, featureGate); err != nil {
314 return fmt.Errorf("apply default configuration: %v", err)
315 }
316
317
318 applyParameters.Store(nil)
319
320 return nil
321 }
322
323
324 func AddFlags(c *LoggingConfiguration, fs *pflag.FlagSet) {
325 addFlags(c, fs)
326 }
327
328
329 func AddGoFlags(c *LoggingConfiguration, fs *flag.FlagSet) {
330 addFlags(c, goFlagSet{FlagSet: fs})
331 }
332
333
334
335 type flagSet interface {
336 BoolVar(p *bool, name string, value bool, usage string)
337 DurationVar(p *time.Duration, name string, value time.Duration, usage string)
338 StringVar(p *string, name string, value string, usage string)
339 Var(value pflag.Value, name string, usage string)
340 VarP(value pflag.Value, name, shorthand, usage string)
341 }
342
343
344 type goFlagSet struct {
345 *flag.FlagSet
346 }
347
348 func (fs goFlagSet) Var(value pflag.Value, name string, usage string) {
349 fs.FlagSet.Var(value, name, usage)
350 }
351
352 func (fs goFlagSet) VarP(value pflag.Value, name, shorthand, usage string) {
353
354 fs.FlagSet.Var(value, name, usage)
355 }
356
357
358
359 func addFlags(c *LoggingConfiguration, fs flagSet) {
360 formats := logRegistry.list()
361 fs.StringVar(&c.Format, "logging-format", c.Format, fmt.Sprintf("Sets the log format. Permitted formats: %s.", formats))
362
363 logRegistry.freeze()
364
365 fs.DurationVar(&c.FlushFrequency.Duration.Duration, LogFlushFreqFlagName, c.FlushFrequency.Duration.Duration, "Maximum number of seconds between log flushes")
366 fs.VarP(VerbosityLevelPflag(&c.Verbosity), "v", "v", "number for the log level verbosity")
367 fs.Var(VModuleConfigurationPflag(&c.VModule), "vmodule", "comma-separated list of pattern=N settings for file-filtered logging (only works for text log format)")
368
369 fs.BoolVar(&c.Options.Text.SplitStream, "log-text-split-stream", false, "[Alpha] In text format, write error messages to stderr and info messages to stdout. The default is to write a single stream to stdout. Enable the LoggingAlphaOptions feature gate to use this.")
370 fs.Var(&c.Options.Text.InfoBufferSize, "log-text-info-buffer-size", "[Alpha] In text 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). Enable the LoggingAlphaOptions feature gate to use this.")
371
372
373
374 if _, err := logRegistry.get("json"); err == nil {
375 fs.BoolVar(&c.Options.JSON.SplitStream, "log-json-split-stream", false, "[Alpha] In JSON format, write error messages to stderr and info messages to stdout. The default is to write a single stream to stdout. Enable the LoggingAlphaOptions feature gate to use this.")
376 fs.Var(&c.Options.JSON.InfoBufferSize, "log-json-info-buffer-size", "[Alpha] 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). Enable the LoggingAlphaOptions feature gate to use this.")
377 }
378 }
379
380
381
382
383
384
385
386 func SetRecommendedLoggingConfiguration(c *LoggingConfiguration) {
387 if c.Format == "" {
388 c.Format = "text"
389 }
390 if c.FlushFrequency.Duration.Duration == 0 {
391 c.FlushFrequency.Duration.Duration = LogFlushFreqDefault
392 c.FlushFrequency.SerializeAsString = true
393 }
394 setRecommendedOutputRouting(&c.Options.Text.OutputRoutingOptions)
395 setRecommendedOutputRouting(&c.Options.JSON.OutputRoutingOptions)
396 }
397
398 func setRecommendedOutputRouting(o *OutputRoutingOptions) {
399 var empty resource.QuantityValue
400 if o.InfoBufferSize == empty {
401 o.InfoBufferSize = resource.QuantityValue{
402
403
404 Quantity: *resource.NewQuantity(0, resource.DecimalSI),
405 }
406
407
408 _ = o.InfoBufferSize.String()
409 }
410 }
411
412
413
414 var loggingFlags pflag.FlagSet
415
416 func init() {
417 var fs flag.FlagSet
418 klogflags.Init(&fs)
419 loggingFlags.AddGoFlagSet(&fs)
420 }
421
422
423 var supportedLogsFlags = map[string]struct{}{
424 "v": {},
425 }
426
427
428
429 func unsupportedLoggingFlags(normalizeFunc func(f *pflag.FlagSet, name string) pflag.NormalizedName) []*pflag.Flag {
430
431 pfs := &pflag.FlagSet{}
432 loggingFlags.VisitAll(func(flag *pflag.Flag) {
433 if _, found := supportedLogsFlags[flag.Name]; !found {
434
435 clone := *flag
436 pfs.AddFlag(&clone)
437 }
438 })
439
440
441 pfs.SetNormalizeFunc(normalizeFunc)
442
443 var allFlags []*pflag.Flag
444 pfs.VisitAll(func(flag *pflag.Flag) {
445 allFlags = append(allFlags, flag)
446 })
447 return allFlags
448 }
449
View as plain text