1/*
2Copyright 2020 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package logs
18
19import (
20	"flag"
21	"fmt"
22	"strings"
23
24	"github.com/go-logr/logr"
25	"github.com/spf13/pflag"
26
27	"k8s.io/component-base/logs/sanitization"
28	"k8s.io/klog/v2"
29)
30
31const (
32	logFormatFlagName = "logging-format"
33	defaultLogFormat  = "text"
34)
35
36// List of logs (k8s.io/klog + k8s.io/component-base/logs) flags supported by all logging formats
37var supportedLogsFlags = map[string]struct{}{
38	"v": {},
39	// TODO: support vmodule after 1.19 Alpha
40}
41
42// Options has klog format parameters
43type Options struct {
44	LogFormat       string
45	LogSanitization bool
46}
47
48// NewOptions return new klog options
49func NewOptions() *Options {
50	return &Options{
51		LogFormat: defaultLogFormat,
52	}
53}
54
55// Validate verifies if any unsupported flag is set
56// for non-default logging format
57func (o *Options) Validate() []error {
58	errs := []error{}
59	if o.LogFormat != defaultLogFormat {
60		allFlags := unsupportedLoggingFlags()
61		for _, fname := range allFlags {
62			if flagIsSet(fname) {
63				errs = append(errs, fmt.Errorf("non-default logging format doesn't honor flag: %s", fname))
64			}
65		}
66	}
67	if _, err := o.Get(); err != nil {
68		errs = append(errs, fmt.Errorf("unsupported log format: %s", o.LogFormat))
69	}
70	return errs
71}
72
73func flagIsSet(name string) bool {
74	f := flag.Lookup(name)
75	if f != nil {
76		return f.DefValue != f.Value.String()
77	}
78	pf := pflag.Lookup(name)
79	if pf != nil {
80		return pf.DefValue != pf.Value.String()
81	}
82	panic("failed to lookup unsupported log flag")
83}
84
85// AddFlags add logging-format flag
86func (o *Options) AddFlags(fs *pflag.FlagSet) {
87	unsupportedFlags := fmt.Sprintf("--%s", strings.Join(unsupportedLoggingFlags(), ", --"))
88	formats := fmt.Sprintf(`"%s"`, strings.Join(logRegistry.List(), `", "`))
89	fs.StringVar(&o.LogFormat, logFormatFlagName, defaultLogFormat, 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))
90
91	// No new log formats should be added after generation is of flag options
92	logRegistry.Freeze()
93	fs.BoolVar(&o.LogSanitization, "experimental-logging-sanitization", o.LogSanitization, `[Experimental] When enabled prevents logging of fields tagged as sensitive (passwords, keys, tokens).
94Runtime log sanitization may introduce significant computation overhead and therefore should not be enabled in production.`)
95}
96
97// Apply set klog logger from LogFormat type
98func (o *Options) Apply() {
99	// if log format not exists, use nil loggr
100	loggr, _ := o.Get()
101	klog.SetLogger(loggr)
102	if o.LogSanitization {
103		klog.SetLogFilter(&sanitization.SanitizingFilter{})
104	}
105}
106
107// Get logger with LogFormat field
108func (o *Options) Get() (logr.Logger, error) {
109	return logRegistry.Get(o.LogFormat)
110}
111
112func unsupportedLoggingFlags() []string {
113	allFlags := []string{}
114
115	// k8s.io/klog flags
116	fs := &flag.FlagSet{}
117	klog.InitFlags(fs)
118	fs.VisitAll(func(flag *flag.Flag) {
119		if _, found := supportedLogsFlags[flag.Name]; !found {
120			allFlags = append(allFlags, flag.Name)
121		}
122	})
123
124	// k8s.io/component-base/logs flags
125	pfs := &pflag.FlagSet{}
126	AddFlags(pfs)
127	pfs.VisitAll(func(flag *pflag.Flag) {
128		if _, found := supportedLogsFlags[flag.Name]; !found {
129			allFlags = append(allFlags, flag.Name)
130		}
131	})
132	return allFlags
133}
134