...

Source file src/github.com/go-logr/logr/funcr/funcr.go

Documentation: github.com/go-logr/logr/funcr

     1  /*
     2  Copyright 2021 The logr Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package funcr implements formatting of structured log messages and
    18  // optionally captures the call site and timestamp.
    19  //
    20  // The simplest way to use it is via its implementation of a
    21  // github.com/go-logr/logr.LogSink with output through an arbitrary
    22  // "write" function.  See New and NewJSON for details.
    23  //
    24  // # Custom LogSinks
    25  //
    26  // For users who need more control, a funcr.Formatter can be embedded inside
    27  // your own custom LogSink implementation. This is useful when the LogSink
    28  // needs to implement additional methods, for example.
    29  //
    30  // # Formatting
    31  //
    32  // This will respect logr.Marshaler, fmt.Stringer, and error interfaces for
    33  // values which are being logged.  When rendering a struct, funcr will use Go's
    34  // standard JSON tags (all except "string").
    35  package funcr
    36  
    37  import (
    38  	"bytes"
    39  	"encoding"
    40  	"encoding/json"
    41  	"fmt"
    42  	"path/filepath"
    43  	"reflect"
    44  	"runtime"
    45  	"strconv"
    46  	"strings"
    47  	"time"
    48  
    49  	"github.com/go-logr/logr"
    50  )
    51  
    52  // New returns a logr.Logger which is implemented by an arbitrary function.
    53  func New(fn func(prefix, args string), opts Options) logr.Logger {
    54  	return logr.New(newSink(fn, NewFormatter(opts)))
    55  }
    56  
    57  // NewJSON returns a logr.Logger which is implemented by an arbitrary function
    58  // and produces JSON output.
    59  func NewJSON(fn func(obj string), opts Options) logr.Logger {
    60  	fnWrapper := func(_, obj string) {
    61  		fn(obj)
    62  	}
    63  	return logr.New(newSink(fnWrapper, NewFormatterJSON(opts)))
    64  }
    65  
    66  // Underlier exposes access to the underlying logging function. Since
    67  // callers only have a logr.Logger, they have to know which
    68  // implementation is in use, so this interface is less of an
    69  // abstraction and more of a way to test type conversion.
    70  type Underlier interface {
    71  	GetUnderlying() func(prefix, args string)
    72  }
    73  
    74  func newSink(fn func(prefix, args string), formatter Formatter) logr.LogSink {
    75  	l := &fnlogger{
    76  		Formatter: formatter,
    77  		write:     fn,
    78  	}
    79  	// For skipping fnlogger.Info and fnlogger.Error.
    80  	l.Formatter.AddCallDepth(1)
    81  	return l
    82  }
    83  
    84  // Options carries parameters which influence the way logs are generated.
    85  type Options struct {
    86  	// LogCaller tells funcr to add a "caller" key to some or all log lines.
    87  	// This has some overhead, so some users might not want it.
    88  	LogCaller MessageClass
    89  
    90  	// LogCallerFunc tells funcr to also log the calling function name.  This
    91  	// has no effect if caller logging is not enabled (see Options.LogCaller).
    92  	LogCallerFunc bool
    93  
    94  	// LogTimestamp tells funcr to add a "ts" key to log lines.  This has some
    95  	// overhead, so some users might not want it.
    96  	LogTimestamp bool
    97  
    98  	// TimestampFormat tells funcr how to render timestamps when LogTimestamp
    99  	// is enabled.  If not specified, a default format will be used.  For more
   100  	// details, see docs for Go's time.Layout.
   101  	TimestampFormat string
   102  
   103  	// LogInfoLevel tells funcr what key to use to log the info level.
   104  	// If not specified, the info level will be logged as "level".
   105  	// If this is set to "", the info level will not be logged at all.
   106  	LogInfoLevel *string
   107  
   108  	// Verbosity tells funcr which V logs to produce.  Higher values enable
   109  	// more logs.  Info logs at or below this level will be written, while logs
   110  	// above this level will be discarded.
   111  	Verbosity int
   112  
   113  	// RenderBuiltinsHook allows users to mutate the list of key-value pairs
   114  	// while a log line is being rendered.  The kvList argument follows logr
   115  	// conventions - each pair of slice elements is comprised of a string key
   116  	// and an arbitrary value (verified and sanitized before calling this
   117  	// hook).  The value returned must follow the same conventions.  This hook
   118  	// can be used to audit or modify logged data.  For example, you might want
   119  	// to prefix all of funcr's built-in keys with some string.  This hook is
   120  	// only called for built-in (provided by funcr itself) key-value pairs.
   121  	// Equivalent hooks are offered for key-value pairs saved via
   122  	// logr.Logger.WithValues or Formatter.AddValues (see RenderValuesHook) and
   123  	// for user-provided pairs (see RenderArgsHook).
   124  	RenderBuiltinsHook func(kvList []any) []any
   125  
   126  	// RenderValuesHook is the same as RenderBuiltinsHook, except that it is
   127  	// only called for key-value pairs saved via logr.Logger.WithValues.  See
   128  	// RenderBuiltinsHook for more details.
   129  	RenderValuesHook func(kvList []any) []any
   130  
   131  	// RenderArgsHook is the same as RenderBuiltinsHook, except that it is only
   132  	// called for key-value pairs passed directly to Info and Error.  See
   133  	// RenderBuiltinsHook for more details.
   134  	RenderArgsHook func(kvList []any) []any
   135  
   136  	// MaxLogDepth tells funcr how many levels of nested fields (e.g. a struct
   137  	// that contains a struct, etc.) it may log.  Every time it finds a struct,
   138  	// slice, array, or map the depth is increased by one.  When the maximum is
   139  	// reached, the value will be converted to a string indicating that the max
   140  	// depth has been exceeded.  If this field is not specified, a default
   141  	// value will be used.
   142  	MaxLogDepth int
   143  }
   144  
   145  // MessageClass indicates which category or categories of messages to consider.
   146  type MessageClass int
   147  
   148  const (
   149  	// None ignores all message classes.
   150  	None MessageClass = iota
   151  	// All considers all message classes.
   152  	All
   153  	// Info only considers info messages.
   154  	Info
   155  	// Error only considers error messages.
   156  	Error
   157  )
   158  
   159  // fnlogger inherits some of its LogSink implementation from Formatter
   160  // and just needs to add some glue code.
   161  type fnlogger struct {
   162  	Formatter
   163  	write func(prefix, args string)
   164  }
   165  
   166  func (l fnlogger) WithName(name string) logr.LogSink {
   167  	l.Formatter.AddName(name)
   168  	return &l
   169  }
   170  
   171  func (l fnlogger) WithValues(kvList ...any) logr.LogSink {
   172  	l.Formatter.AddValues(kvList)
   173  	return &l
   174  }
   175  
   176  func (l fnlogger) WithCallDepth(depth int) logr.LogSink {
   177  	l.Formatter.AddCallDepth(depth)
   178  	return &l
   179  }
   180  
   181  func (l fnlogger) Info(level int, msg string, kvList ...any) {
   182  	prefix, args := l.FormatInfo(level, msg, kvList)
   183  	l.write(prefix, args)
   184  }
   185  
   186  func (l fnlogger) Error(err error, msg string, kvList ...any) {
   187  	prefix, args := l.FormatError(err, msg, kvList)
   188  	l.write(prefix, args)
   189  }
   190  
   191  func (l fnlogger) GetUnderlying() func(prefix, args string) {
   192  	return l.write
   193  }
   194  
   195  // Assert conformance to the interfaces.
   196  var _ logr.LogSink = &fnlogger{}
   197  var _ logr.CallDepthLogSink = &fnlogger{}
   198  var _ Underlier = &fnlogger{}
   199  
   200  // NewFormatter constructs a Formatter which emits a JSON-like key=value format.
   201  func NewFormatter(opts Options) Formatter {
   202  	return newFormatter(opts, outputKeyValue)
   203  }
   204  
   205  // NewFormatterJSON constructs a Formatter which emits strict JSON.
   206  func NewFormatterJSON(opts Options) Formatter {
   207  	return newFormatter(opts, outputJSON)
   208  }
   209  
   210  // Defaults for Options.
   211  const defaultTimestampFormat = "2006-01-02 15:04:05.000000"
   212  const defaultMaxLogDepth = 16
   213  
   214  func newFormatter(opts Options, outfmt outputFormat) Formatter {
   215  	if opts.TimestampFormat == "" {
   216  		opts.TimestampFormat = defaultTimestampFormat
   217  	}
   218  	if opts.MaxLogDepth == 0 {
   219  		opts.MaxLogDepth = defaultMaxLogDepth
   220  	}
   221  	if opts.LogInfoLevel == nil {
   222  		opts.LogInfoLevel = new(string)
   223  		*opts.LogInfoLevel = "level"
   224  	}
   225  	f := Formatter{
   226  		outputFormat: outfmt,
   227  		prefix:       "",
   228  		values:       nil,
   229  		depth:        0,
   230  		opts:         &opts,
   231  	}
   232  	return f
   233  }
   234  
   235  // Formatter is an opaque struct which can be embedded in a LogSink
   236  // implementation. It should be constructed with NewFormatter. Some of
   237  // its methods directly implement logr.LogSink.
   238  type Formatter struct {
   239  	outputFormat outputFormat
   240  	prefix       string
   241  	values       []any
   242  	valuesStr    string
   243  	depth        int
   244  	opts         *Options
   245  	groupName    string // for slog groups
   246  	groups       []groupDef
   247  }
   248  
   249  // outputFormat indicates which outputFormat to use.
   250  type outputFormat int
   251  
   252  const (
   253  	// outputKeyValue emits a JSON-like key=value format, but not strict JSON.
   254  	outputKeyValue outputFormat = iota
   255  	// outputJSON emits strict JSON.
   256  	outputJSON
   257  )
   258  
   259  // groupDef represents a saved group.  The values may be empty, but we don't
   260  // know if we need to render the group until the final record is rendered.
   261  type groupDef struct {
   262  	name   string
   263  	values string
   264  }
   265  
   266  // PseudoStruct is a list of key-value pairs that gets logged as a struct.
   267  type PseudoStruct []any
   268  
   269  // render produces a log line, ready to use.
   270  func (f Formatter) render(builtins, args []any) string {
   271  	// Empirically bytes.Buffer is faster than strings.Builder for this.
   272  	buf := bytes.NewBuffer(make([]byte, 0, 1024))
   273  
   274  	if f.outputFormat == outputJSON {
   275  		buf.WriteByte('{') // for the whole record
   276  	}
   277  
   278  	// Render builtins
   279  	vals := builtins
   280  	if hook := f.opts.RenderBuiltinsHook; hook != nil {
   281  		vals = hook(f.sanitize(vals))
   282  	}
   283  	f.flatten(buf, vals, false) // keys are ours, no need to escape
   284  	continuing := len(builtins) > 0
   285  
   286  	// Turn the inner-most group into a string
   287  	argsStr := func() string {
   288  		buf := bytes.NewBuffer(make([]byte, 0, 1024))
   289  
   290  		vals = args
   291  		if hook := f.opts.RenderArgsHook; hook != nil {
   292  			vals = hook(f.sanitize(vals))
   293  		}
   294  		f.flatten(buf, vals, true) // escape user-provided keys
   295  
   296  		return buf.String()
   297  	}()
   298  
   299  	// Render the stack of groups from the inside out.
   300  	bodyStr := f.renderGroup(f.groupName, f.valuesStr, argsStr)
   301  	for i := len(f.groups) - 1; i >= 0; i-- {
   302  		grp := &f.groups[i]
   303  		if grp.values == "" && bodyStr == "" {
   304  			// no contents, so we must elide the whole group
   305  			continue
   306  		}
   307  		bodyStr = f.renderGroup(grp.name, grp.values, bodyStr)
   308  	}
   309  
   310  	if bodyStr != "" {
   311  		if continuing {
   312  			buf.WriteByte(f.comma())
   313  		}
   314  		buf.WriteString(bodyStr)
   315  	}
   316  
   317  	if f.outputFormat == outputJSON {
   318  		buf.WriteByte('}') // for the whole record
   319  	}
   320  
   321  	return buf.String()
   322  }
   323  
   324  // renderGroup returns a string representation of the named group with rendered
   325  // values and args.  If the name is empty, this will return the values and args,
   326  // joined.  If the name is not empty, this will return a single key-value pair,
   327  // where the value is a grouping of the values and args.  If the values and
   328  // args are both empty, this will return an empty string, even if the name was
   329  // specified.
   330  func (f Formatter) renderGroup(name string, values string, args string) string {
   331  	buf := bytes.NewBuffer(make([]byte, 0, 1024))
   332  
   333  	needClosingBrace := false
   334  	if name != "" && (values != "" || args != "") {
   335  		buf.WriteString(f.quoted(name, true)) // escape user-provided keys
   336  		buf.WriteByte(f.colon())
   337  		buf.WriteByte('{')
   338  		needClosingBrace = true
   339  	}
   340  
   341  	continuing := false
   342  	if values != "" {
   343  		buf.WriteString(values)
   344  		continuing = true
   345  	}
   346  
   347  	if args != "" {
   348  		if continuing {
   349  			buf.WriteByte(f.comma())
   350  		}
   351  		buf.WriteString(args)
   352  	}
   353  
   354  	if needClosingBrace {
   355  		buf.WriteByte('}')
   356  	}
   357  
   358  	return buf.String()
   359  }
   360  
   361  // flatten renders a list of key-value pairs into a buffer.  If escapeKeys is
   362  // true, the keys are assumed to have non-JSON-compatible characters in them
   363  // and must be evaluated for escapes.
   364  //
   365  // This function returns a potentially modified version of kvList, which
   366  // ensures that there is a value for every key (adding a value if needed) and
   367  // that each key is a string (substituting a key if needed).
   368  func (f Formatter) flatten(buf *bytes.Buffer, kvList []any, escapeKeys bool) []any {
   369  	// This logic overlaps with sanitize() but saves one type-cast per key,
   370  	// which can be measurable.
   371  	if len(kvList)%2 != 0 {
   372  		kvList = append(kvList, noValue)
   373  	}
   374  	copied := false
   375  	for i := 0; i < len(kvList); i += 2 {
   376  		k, ok := kvList[i].(string)
   377  		if !ok {
   378  			if !copied {
   379  				newList := make([]any, len(kvList))
   380  				copy(newList, kvList)
   381  				kvList = newList
   382  				copied = true
   383  			}
   384  			k = f.nonStringKey(kvList[i])
   385  			kvList[i] = k
   386  		}
   387  		v := kvList[i+1]
   388  
   389  		if i > 0 {
   390  			if f.outputFormat == outputJSON {
   391  				buf.WriteByte(f.comma())
   392  			} else {
   393  				// In theory the format could be something we don't understand.  In
   394  				// practice, we control it, so it won't be.
   395  				buf.WriteByte(' ')
   396  			}
   397  		}
   398  
   399  		buf.WriteString(f.quoted(k, escapeKeys))
   400  		buf.WriteByte(f.colon())
   401  		buf.WriteString(f.pretty(v))
   402  	}
   403  	return kvList
   404  }
   405  
   406  func (f Formatter) quoted(str string, escape bool) string {
   407  	if escape {
   408  		return prettyString(str)
   409  	}
   410  	// this is faster
   411  	return `"` + str + `"`
   412  }
   413  
   414  func (f Formatter) comma() byte {
   415  	if f.outputFormat == outputJSON {
   416  		return ','
   417  	}
   418  	return ' '
   419  }
   420  
   421  func (f Formatter) colon() byte {
   422  	if f.outputFormat == outputJSON {
   423  		return ':'
   424  	}
   425  	return '='
   426  }
   427  
   428  func (f Formatter) pretty(value any) string {
   429  	return f.prettyWithFlags(value, 0, 0)
   430  }
   431  
   432  const (
   433  	flagRawStruct = 0x1 // do not print braces on structs
   434  )
   435  
   436  // TODO: This is not fast. Most of the overhead goes here.
   437  func (f Formatter) prettyWithFlags(value any, flags uint32, depth int) string {
   438  	if depth > f.opts.MaxLogDepth {
   439  		return `"<max-log-depth-exceeded>"`
   440  	}
   441  
   442  	// Handle types that take full control of logging.
   443  	if v, ok := value.(logr.Marshaler); ok {
   444  		// Replace the value with what the type wants to get logged.
   445  		// That then gets handled below via reflection.
   446  		value = invokeMarshaler(v)
   447  	}
   448  
   449  	// Handle types that want to format themselves.
   450  	switch v := value.(type) {
   451  	case fmt.Stringer:
   452  		value = invokeStringer(v)
   453  	case error:
   454  		value = invokeError(v)
   455  	}
   456  
   457  	// Handling the most common types without reflect is a small perf win.
   458  	switch v := value.(type) {
   459  	case bool:
   460  		return strconv.FormatBool(v)
   461  	case string:
   462  		return prettyString(v)
   463  	case int:
   464  		return strconv.FormatInt(int64(v), 10)
   465  	case int8:
   466  		return strconv.FormatInt(int64(v), 10)
   467  	case int16:
   468  		return strconv.FormatInt(int64(v), 10)
   469  	case int32:
   470  		return strconv.FormatInt(int64(v), 10)
   471  	case int64:
   472  		return strconv.FormatInt(int64(v), 10)
   473  	case uint:
   474  		return strconv.FormatUint(uint64(v), 10)
   475  	case uint8:
   476  		return strconv.FormatUint(uint64(v), 10)
   477  	case uint16:
   478  		return strconv.FormatUint(uint64(v), 10)
   479  	case uint32:
   480  		return strconv.FormatUint(uint64(v), 10)
   481  	case uint64:
   482  		return strconv.FormatUint(v, 10)
   483  	case uintptr:
   484  		return strconv.FormatUint(uint64(v), 10)
   485  	case float32:
   486  		return strconv.FormatFloat(float64(v), 'f', -1, 32)
   487  	case float64:
   488  		return strconv.FormatFloat(v, 'f', -1, 64)
   489  	case complex64:
   490  		return `"` + strconv.FormatComplex(complex128(v), 'f', -1, 64) + `"`
   491  	case complex128:
   492  		return `"` + strconv.FormatComplex(v, 'f', -1, 128) + `"`
   493  	case PseudoStruct:
   494  		buf := bytes.NewBuffer(make([]byte, 0, 1024))
   495  		v = f.sanitize(v)
   496  		if flags&flagRawStruct == 0 {
   497  			buf.WriteByte('{')
   498  		}
   499  		for i := 0; i < len(v); i += 2 {
   500  			if i > 0 {
   501  				buf.WriteByte(f.comma())
   502  			}
   503  			k, _ := v[i].(string) // sanitize() above means no need to check success
   504  			// arbitrary keys might need escaping
   505  			buf.WriteString(prettyString(k))
   506  			buf.WriteByte(f.colon())
   507  			buf.WriteString(f.prettyWithFlags(v[i+1], 0, depth+1))
   508  		}
   509  		if flags&flagRawStruct == 0 {
   510  			buf.WriteByte('}')
   511  		}
   512  		return buf.String()
   513  	}
   514  
   515  	buf := bytes.NewBuffer(make([]byte, 0, 256))
   516  	t := reflect.TypeOf(value)
   517  	if t == nil {
   518  		return "null"
   519  	}
   520  	v := reflect.ValueOf(value)
   521  	switch t.Kind() {
   522  	case reflect.Bool:
   523  		return strconv.FormatBool(v.Bool())
   524  	case reflect.String:
   525  		return prettyString(v.String())
   526  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   527  		return strconv.FormatInt(int64(v.Int()), 10)
   528  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
   529  		return strconv.FormatUint(uint64(v.Uint()), 10)
   530  	case reflect.Float32:
   531  		return strconv.FormatFloat(float64(v.Float()), 'f', -1, 32)
   532  	case reflect.Float64:
   533  		return strconv.FormatFloat(v.Float(), 'f', -1, 64)
   534  	case reflect.Complex64:
   535  		return `"` + strconv.FormatComplex(complex128(v.Complex()), 'f', -1, 64) + `"`
   536  	case reflect.Complex128:
   537  		return `"` + strconv.FormatComplex(v.Complex(), 'f', -1, 128) + `"`
   538  	case reflect.Struct:
   539  		if flags&flagRawStruct == 0 {
   540  			buf.WriteByte('{')
   541  		}
   542  		printComma := false // testing i>0 is not enough because of JSON omitted fields
   543  		for i := 0; i < t.NumField(); i++ {
   544  			fld := t.Field(i)
   545  			if fld.PkgPath != "" {
   546  				// reflect says this field is only defined for non-exported fields.
   547  				continue
   548  			}
   549  			if !v.Field(i).CanInterface() {
   550  				// reflect isn't clear exactly what this means, but we can't use it.
   551  				continue
   552  			}
   553  			name := ""
   554  			omitempty := false
   555  			if tag, found := fld.Tag.Lookup("json"); found {
   556  				if tag == "-" {
   557  					continue
   558  				}
   559  				if comma := strings.Index(tag, ","); comma != -1 {
   560  					if n := tag[:comma]; n != "" {
   561  						name = n
   562  					}
   563  					rest := tag[comma:]
   564  					if strings.Contains(rest, ",omitempty,") || strings.HasSuffix(rest, ",omitempty") {
   565  						omitempty = true
   566  					}
   567  				} else {
   568  					name = tag
   569  				}
   570  			}
   571  			if omitempty && isEmpty(v.Field(i)) {
   572  				continue
   573  			}
   574  			if printComma {
   575  				buf.WriteByte(f.comma())
   576  			}
   577  			printComma = true // if we got here, we are rendering a field
   578  			if fld.Anonymous && fld.Type.Kind() == reflect.Struct && name == "" {
   579  				buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), flags|flagRawStruct, depth+1))
   580  				continue
   581  			}
   582  			if name == "" {
   583  				name = fld.Name
   584  			}
   585  			// field names can't contain characters which need escaping
   586  			buf.WriteString(f.quoted(name, false))
   587  			buf.WriteByte(f.colon())
   588  			buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), 0, depth+1))
   589  		}
   590  		if flags&flagRawStruct == 0 {
   591  			buf.WriteByte('}')
   592  		}
   593  		return buf.String()
   594  	case reflect.Slice, reflect.Array:
   595  		// If this is outputing as JSON make sure this isn't really a json.RawMessage.
   596  		// If so just emit "as-is" and don't pretty it as that will just print
   597  		// it as [X,Y,Z,...] which isn't terribly useful vs the string form you really want.
   598  		if f.outputFormat == outputJSON {
   599  			if rm, ok := value.(json.RawMessage); ok {
   600  				// If it's empty make sure we emit an empty value as the array style would below.
   601  				if len(rm) > 0 {
   602  					buf.Write(rm)
   603  				} else {
   604  					buf.WriteString("null")
   605  				}
   606  				return buf.String()
   607  			}
   608  		}
   609  		buf.WriteByte('[')
   610  		for i := 0; i < v.Len(); i++ {
   611  			if i > 0 {
   612  				buf.WriteByte(f.comma())
   613  			}
   614  			e := v.Index(i)
   615  			buf.WriteString(f.prettyWithFlags(e.Interface(), 0, depth+1))
   616  		}
   617  		buf.WriteByte(']')
   618  		return buf.String()
   619  	case reflect.Map:
   620  		buf.WriteByte('{')
   621  		// This does not sort the map keys, for best perf.
   622  		it := v.MapRange()
   623  		i := 0
   624  		for it.Next() {
   625  			if i > 0 {
   626  				buf.WriteByte(f.comma())
   627  			}
   628  			// If a map key supports TextMarshaler, use it.
   629  			keystr := ""
   630  			if m, ok := it.Key().Interface().(encoding.TextMarshaler); ok {
   631  				txt, err := m.MarshalText()
   632  				if err != nil {
   633  					keystr = fmt.Sprintf("<error-MarshalText: %s>", err.Error())
   634  				} else {
   635  					keystr = string(txt)
   636  				}
   637  				keystr = prettyString(keystr)
   638  			} else {
   639  				// prettyWithFlags will produce already-escaped values
   640  				keystr = f.prettyWithFlags(it.Key().Interface(), 0, depth+1)
   641  				if t.Key().Kind() != reflect.String {
   642  					// JSON only does string keys.  Unlike Go's standard JSON, we'll
   643  					// convert just about anything to a string.
   644  					keystr = prettyString(keystr)
   645  				}
   646  			}
   647  			buf.WriteString(keystr)
   648  			buf.WriteByte(f.colon())
   649  			buf.WriteString(f.prettyWithFlags(it.Value().Interface(), 0, depth+1))
   650  			i++
   651  		}
   652  		buf.WriteByte('}')
   653  		return buf.String()
   654  	case reflect.Ptr, reflect.Interface:
   655  		if v.IsNil() {
   656  			return "null"
   657  		}
   658  		return f.prettyWithFlags(v.Elem().Interface(), 0, depth)
   659  	}
   660  	return fmt.Sprintf(`"<unhandled-%s>"`, t.Kind().String())
   661  }
   662  
   663  func prettyString(s string) string {
   664  	// Avoid escaping (which does allocations) if we can.
   665  	if needsEscape(s) {
   666  		return strconv.Quote(s)
   667  	}
   668  	b := bytes.NewBuffer(make([]byte, 0, 1024))
   669  	b.WriteByte('"')
   670  	b.WriteString(s)
   671  	b.WriteByte('"')
   672  	return b.String()
   673  }
   674  
   675  // needsEscape determines whether the input string needs to be escaped or not,
   676  // without doing any allocations.
   677  func needsEscape(s string) bool {
   678  	for _, r := range s {
   679  		if !strconv.IsPrint(r) || r == '\\' || r == '"' {
   680  			return true
   681  		}
   682  	}
   683  	return false
   684  }
   685  
   686  func isEmpty(v reflect.Value) bool {
   687  	switch v.Kind() {
   688  	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
   689  		return v.Len() == 0
   690  	case reflect.Bool:
   691  		return !v.Bool()
   692  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   693  		return v.Int() == 0
   694  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
   695  		return v.Uint() == 0
   696  	case reflect.Float32, reflect.Float64:
   697  		return v.Float() == 0
   698  	case reflect.Complex64, reflect.Complex128:
   699  		return v.Complex() == 0
   700  	case reflect.Interface, reflect.Ptr:
   701  		return v.IsNil()
   702  	}
   703  	return false
   704  }
   705  
   706  func invokeMarshaler(m logr.Marshaler) (ret any) {
   707  	defer func() {
   708  		if r := recover(); r != nil {
   709  			ret = fmt.Sprintf("<panic: %s>", r)
   710  		}
   711  	}()
   712  	return m.MarshalLog()
   713  }
   714  
   715  func invokeStringer(s fmt.Stringer) (ret string) {
   716  	defer func() {
   717  		if r := recover(); r != nil {
   718  			ret = fmt.Sprintf("<panic: %s>", r)
   719  		}
   720  	}()
   721  	return s.String()
   722  }
   723  
   724  func invokeError(e error) (ret string) {
   725  	defer func() {
   726  		if r := recover(); r != nil {
   727  			ret = fmt.Sprintf("<panic: %s>", r)
   728  		}
   729  	}()
   730  	return e.Error()
   731  }
   732  
   733  // Caller represents the original call site for a log line, after considering
   734  // logr.Logger.WithCallDepth and logr.Logger.WithCallStackHelper.  The File and
   735  // Line fields will always be provided, while the Func field is optional.
   736  // Users can set the render hook fields in Options to examine logged key-value
   737  // pairs, one of which will be {"caller", Caller} if the Options.LogCaller
   738  // field is enabled for the given MessageClass.
   739  type Caller struct {
   740  	// File is the basename of the file for this call site.
   741  	File string `json:"file"`
   742  	// Line is the line number in the file for this call site.
   743  	Line int `json:"line"`
   744  	// Func is the function name for this call site, or empty if
   745  	// Options.LogCallerFunc is not enabled.
   746  	Func string `json:"function,omitempty"`
   747  }
   748  
   749  func (f Formatter) caller() Caller {
   750  	// +1 for this frame, +1 for Info/Error.
   751  	pc, file, line, ok := runtime.Caller(f.depth + 2)
   752  	if !ok {
   753  		return Caller{"<unknown>", 0, ""}
   754  	}
   755  	fn := ""
   756  	if f.opts.LogCallerFunc {
   757  		if fp := runtime.FuncForPC(pc); fp != nil {
   758  			fn = fp.Name()
   759  		}
   760  	}
   761  
   762  	return Caller{filepath.Base(file), line, fn}
   763  }
   764  
   765  const noValue = "<no-value>"
   766  
   767  func (f Formatter) nonStringKey(v any) string {
   768  	return fmt.Sprintf("<non-string-key: %s>", f.snippet(v))
   769  }
   770  
   771  // snippet produces a short snippet string of an arbitrary value.
   772  func (f Formatter) snippet(v any) string {
   773  	const snipLen = 16
   774  
   775  	snip := f.pretty(v)
   776  	if len(snip) > snipLen {
   777  		snip = snip[:snipLen]
   778  	}
   779  	return snip
   780  }
   781  
   782  // sanitize ensures that a list of key-value pairs has a value for every key
   783  // (adding a value if needed) and that each key is a string (substituting a key
   784  // if needed).
   785  func (f Formatter) sanitize(kvList []any) []any {
   786  	if len(kvList)%2 != 0 {
   787  		kvList = append(kvList, noValue)
   788  	}
   789  	for i := 0; i < len(kvList); i += 2 {
   790  		_, ok := kvList[i].(string)
   791  		if !ok {
   792  			kvList[i] = f.nonStringKey(kvList[i])
   793  		}
   794  	}
   795  	return kvList
   796  }
   797  
   798  // startGroup opens a new group scope (basically a sub-struct), which locks all
   799  // the current saved values and starts them anew.  This is needed to satisfy
   800  // slog.
   801  func (f *Formatter) startGroup(name string) {
   802  	// Unnamed groups are just inlined.
   803  	if name == "" {
   804  		return
   805  	}
   806  
   807  	n := len(f.groups)
   808  	f.groups = append(f.groups[:n:n], groupDef{f.groupName, f.valuesStr})
   809  
   810  	// Start collecting new values.
   811  	f.groupName = name
   812  	f.valuesStr = ""
   813  	f.values = nil
   814  }
   815  
   816  // Init configures this Formatter from runtime info, such as the call depth
   817  // imposed by logr itself.
   818  // Note that this receiver is a pointer, so depth can be saved.
   819  func (f *Formatter) Init(info logr.RuntimeInfo) {
   820  	f.depth += info.CallDepth
   821  }
   822  
   823  // Enabled checks whether an info message at the given level should be logged.
   824  func (f Formatter) Enabled(level int) bool {
   825  	return level <= f.opts.Verbosity
   826  }
   827  
   828  // GetDepth returns the current depth of this Formatter.  This is useful for
   829  // implementations which do their own caller attribution.
   830  func (f Formatter) GetDepth() int {
   831  	return f.depth
   832  }
   833  
   834  // FormatInfo renders an Info log message into strings.  The prefix will be
   835  // empty when no names were set (via AddNames), or when the output is
   836  // configured for JSON.
   837  func (f Formatter) FormatInfo(level int, msg string, kvList []any) (prefix, argsStr string) {
   838  	args := make([]any, 0, 64) // using a constant here impacts perf
   839  	prefix = f.prefix
   840  	if f.outputFormat == outputJSON {
   841  		args = append(args, "logger", prefix)
   842  		prefix = ""
   843  	}
   844  	if f.opts.LogTimestamp {
   845  		args = append(args, "ts", time.Now().Format(f.opts.TimestampFormat))
   846  	}
   847  	if policy := f.opts.LogCaller; policy == All || policy == Info {
   848  		args = append(args, "caller", f.caller())
   849  	}
   850  	if key := *f.opts.LogInfoLevel; key != "" {
   851  		args = append(args, key, level)
   852  	}
   853  	args = append(args, "msg", msg)
   854  	return prefix, f.render(args, kvList)
   855  }
   856  
   857  // FormatError renders an Error log message into strings.  The prefix will be
   858  // empty when no names were set (via AddNames), or when the output is
   859  // configured for JSON.
   860  func (f Formatter) FormatError(err error, msg string, kvList []any) (prefix, argsStr string) {
   861  	args := make([]any, 0, 64) // using a constant here impacts perf
   862  	prefix = f.prefix
   863  	if f.outputFormat == outputJSON {
   864  		args = append(args, "logger", prefix)
   865  		prefix = ""
   866  	}
   867  	if f.opts.LogTimestamp {
   868  		args = append(args, "ts", time.Now().Format(f.opts.TimestampFormat))
   869  	}
   870  	if policy := f.opts.LogCaller; policy == All || policy == Error {
   871  		args = append(args, "caller", f.caller())
   872  	}
   873  	args = append(args, "msg", msg)
   874  	var loggableErr any
   875  	if err != nil {
   876  		loggableErr = err.Error()
   877  	}
   878  	args = append(args, "error", loggableErr)
   879  	return prefix, f.render(args, kvList)
   880  }
   881  
   882  // AddName appends the specified name.  funcr uses '/' characters to separate
   883  // name elements.  Callers should not pass '/' in the provided name string, but
   884  // this library does not actually enforce that.
   885  func (f *Formatter) AddName(name string) {
   886  	if len(f.prefix) > 0 {
   887  		f.prefix += "/"
   888  	}
   889  	f.prefix += name
   890  }
   891  
   892  // AddValues adds key-value pairs to the set of saved values to be logged with
   893  // each log line.
   894  func (f *Formatter) AddValues(kvList []any) {
   895  	// Three slice args forces a copy.
   896  	n := len(f.values)
   897  	f.values = append(f.values[:n:n], kvList...)
   898  
   899  	vals := f.values
   900  	if hook := f.opts.RenderValuesHook; hook != nil {
   901  		vals = hook(f.sanitize(vals))
   902  	}
   903  
   904  	// Pre-render values, so we don't have to do it on each Info/Error call.
   905  	buf := bytes.NewBuffer(make([]byte, 0, 1024))
   906  	f.flatten(buf, vals, true) // escape user-provided keys
   907  	f.valuesStr = buf.String()
   908  }
   909  
   910  // AddCallDepth increases the number of stack-frames to skip when attributing
   911  // the log line to a file and line.
   912  func (f *Formatter) AddCallDepth(depth int) {
   913  	f.depth += depth
   914  }
   915  

View as plain text