...

Source file src/k8s.io/klog/v2/textlogger/textlogger.go

Documentation: k8s.io/klog/v2/textlogger

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  Copyright 2020 Intel Corporation.
     4  
     5  Licensed under the Apache License, Version 2.0 (the "License");
     6  you may not use this file except in compliance with the License.
     7  You may obtain a copy of the License at
     8  
     9      http://www.apache.org/licenses/LICENSE-2.0
    10  
    11  Unless required by applicable law or agreed to in writing, software
    12  distributed under the License is distributed on an "AS IS" BASIS,
    13  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  See the License for the specific language governing permissions and
    15  limitations under the License.
    16  */
    17  
    18  // Package textlogger contains an implementation of the logr interface which is
    19  // producing the exact same output as klog. It does not route output through
    20  // klog (i.e. ignores [k8s.io/klog/v2.InitFlags]). Instead, all settings must be
    21  // configured through its own [NewConfig] and [Config.AddFlags].
    22  package textlogger
    23  
    24  import (
    25  	"runtime"
    26  	"strconv"
    27  	"strings"
    28  	"time"
    29  
    30  	"github.com/go-logr/logr"
    31  
    32  	"k8s.io/klog/v2/internal/buffer"
    33  	"k8s.io/klog/v2/internal/serialize"
    34  	"k8s.io/klog/v2/internal/severity"
    35  	"k8s.io/klog/v2/internal/verbosity"
    36  )
    37  
    38  var (
    39  	// TimeNow is used to retrieve the current time. May be changed for testing.
    40  	TimeNow = time.Now
    41  )
    42  
    43  const (
    44  	// nameKey is used to log the `WithName` values as an additional attribute.
    45  	nameKey = "logger"
    46  )
    47  
    48  // NewLogger constructs a new logger.
    49  //
    50  // Verbosity can be modified at any time through the Config.V and
    51  // Config.VModule API.
    52  func NewLogger(c *Config) logr.Logger {
    53  	return logr.New(&tlogger{
    54  		values: nil,
    55  		config: c,
    56  	})
    57  }
    58  
    59  type tlogger struct {
    60  	callDepth int
    61  
    62  	// hasPrefix is true if the first entry in values is the special
    63  	// nameKey key/value. Such an entry gets added and later updated in
    64  	// WithName.
    65  	hasPrefix bool
    66  
    67  	values []interface{}
    68  	groups string
    69  	config *Config
    70  }
    71  
    72  func (l *tlogger) Init(info logr.RuntimeInfo) {
    73  	l.callDepth = info.CallDepth
    74  }
    75  
    76  func (l *tlogger) WithCallDepth(depth int) logr.LogSink {
    77  	newLogger := *l
    78  	newLogger.callDepth += depth
    79  	return &newLogger
    80  }
    81  
    82  func (l *tlogger) Enabled(level int) bool {
    83  	return l.config.vstate.Enabled(verbosity.Level(level), 1+l.callDepth)
    84  }
    85  
    86  func (l *tlogger) Info(_ int, msg string, kvList ...interface{}) {
    87  	l.print(nil, severity.InfoLog, msg, kvList)
    88  }
    89  
    90  func (l *tlogger) Error(err error, msg string, kvList ...interface{}) {
    91  	l.print(err, severity.ErrorLog, msg, kvList)
    92  }
    93  
    94  func (l *tlogger) print(err error, s severity.Severity, msg string, kvList []interface{}) {
    95  	// Determine caller.
    96  	// +1 for this frame, +1 for Info/Error.
    97  	skip := l.callDepth + 2
    98  	file, line := l.config.co.unwind(skip)
    99  	if file == "" {
   100  		file = "???"
   101  		line = 1
   102  	} else if slash := strings.LastIndex(file, "/"); slash >= 0 {
   103  		file = file[slash+1:]
   104  	}
   105  	l.printWithInfos(file, line, time.Now(), err, s, msg, kvList)
   106  }
   107  
   108  func runtimeBacktrace(skip int) (string, int) {
   109  	_, file, line, ok := runtime.Caller(skip + 1)
   110  	if !ok {
   111  		return "", 0
   112  	}
   113  	return file, line
   114  }
   115  
   116  func (l *tlogger) printWithInfos(file string, line int, now time.Time, err error, s severity.Severity, msg string, kvList []interface{}) {
   117  	// Only create a new buffer if we don't have one cached.
   118  	b := buffer.GetBuffer()
   119  	defer buffer.PutBuffer(b)
   120  
   121  	// Format header.
   122  	if l.config.co.fixedTime != nil {
   123  		now = *l.config.co.fixedTime
   124  	}
   125  	b.FormatHeader(s, file, line, now)
   126  
   127  	// The message is always quoted, even if it contains line breaks.
   128  	// If developers want multi-line output, they should use a small, fixed
   129  	// message and put the multi-line output into a value.
   130  	b.WriteString(strconv.Quote(msg))
   131  	if err != nil {
   132  		serialize.KVFormat(&b.Buffer, "err", err)
   133  	}
   134  	serialize.MergeAndFormatKVs(&b.Buffer, l.values, kvList)
   135  	if b.Len() == 0 || b.Bytes()[b.Len()-1] != '\n' {
   136  		b.WriteByte('\n')
   137  	}
   138  	_, _ = l.config.co.output.Write(b.Bytes())
   139  }
   140  
   141  func (l *tlogger) WriteKlogBuffer(data []byte) {
   142  	_, _ = l.config.co.output.Write(data)
   143  }
   144  
   145  // WithName returns a new logr.Logger with the specified name appended.  klogr
   146  // uses '/' characters to separate name elements.  Callers should not pass '/'
   147  // in the provided name string, but this library does not actually enforce that.
   148  func (l *tlogger) WithName(name string) logr.LogSink {
   149  	clone := *l
   150  	if l.hasPrefix {
   151  		// Copy slice and modify value. No length checks and type
   152  		// assertions are needed because hasPrefix is only true if the
   153  		// first two elements exist and are key/value strings.
   154  		v := make([]interface{}, 0, len(l.values))
   155  		v = append(v, l.values...)
   156  		prefix, _ := v[1].(string)
   157  		v[1] = prefix + "." + name
   158  		clone.values = v
   159  	} else {
   160  		// Preprend new key/value pair.
   161  		v := make([]interface{}, 0, 2+len(l.values))
   162  		v = append(v, nameKey, name)
   163  		v = append(v, l.values...)
   164  		clone.values = v
   165  		clone.hasPrefix = true
   166  	}
   167  	return &clone
   168  }
   169  
   170  func (l *tlogger) WithValues(kvList ...interface{}) logr.LogSink {
   171  	clone := *l
   172  	clone.values = serialize.WithValues(l.values, kvList)
   173  	return &clone
   174  }
   175  
   176  // KlogBufferWriter is implemented by the textlogger LogSink.
   177  type KlogBufferWriter interface {
   178  	// WriteKlogBuffer takes a pre-formatted buffer prepared by klog and
   179  	// writes it unchanged to the output stream. Can be used with
   180  	// klog.WriteKlogBuffer when setting a logger through
   181  	// klog.SetLoggerWithOptions.
   182  	WriteKlogBuffer([]byte)
   183  }
   184  
   185  var _ logr.LogSink = &tlogger{}
   186  var _ logr.CallDepthLogSink = &tlogger{}
   187  var _ KlogBufferWriter = &tlogger{}
   188  

View as plain text