funcr.go raw

   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.AddCallDepth(1) // via Formatter
  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.AddName(name) // via Formatter
 168  	return &l
 169  }
 170  
 171  func (l fnlogger) WithValues(kvList ...any) logr.LogSink {
 172  	l.AddValues(kvList) // via Formatter
 173  	return &l
 174  }
 175  
 176  func (l fnlogger) WithCallDepth(depth int) logr.LogSink {
 177  	l.AddCallDepth(depth) // via Formatter
 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