funcr.go (24487B)
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 // Verbosity tells funcr which V logs to produce. Higher values enable 104 // more logs. Info logs at or below this level will be written, while logs 105 // above this level will be discarded. 106 Verbosity int 107 108 // RenderBuiltinsHook allows users to mutate the list of key-value pairs 109 // while a log line is being rendered. The kvList argument follows logr 110 // conventions - each pair of slice elements is comprised of a string key 111 // and an arbitrary value (verified and sanitized before calling this 112 // hook). The value returned must follow the same conventions. This hook 113 // can be used to audit or modify logged data. For example, you might want 114 // to prefix all of funcr's built-in keys with some string. This hook is 115 // only called for built-in (provided by funcr itself) key-value pairs. 116 // Equivalent hooks are offered for key-value pairs saved via 117 // logr.Logger.WithValues or Formatter.AddValues (see RenderValuesHook) and 118 // for user-provided pairs (see RenderArgsHook). 119 RenderBuiltinsHook func(kvList []interface{}) []interface{} 120 121 // RenderValuesHook is the same as RenderBuiltinsHook, except that it is 122 // only called for key-value pairs saved via logr.Logger.WithValues. See 123 // RenderBuiltinsHook for more details. 124 RenderValuesHook func(kvList []interface{}) []interface{} 125 126 // RenderArgsHook is the same as RenderBuiltinsHook, except that it is only 127 // called for key-value pairs passed directly to Info and Error. See 128 // RenderBuiltinsHook for more details. 129 RenderArgsHook func(kvList []interface{}) []interface{} 130 131 // MaxLogDepth tells funcr how many levels of nested fields (e.g. a struct 132 // that contains a struct, etc.) it may log. Every time it finds a struct, 133 // slice, array, or map the depth is increased by one. When the maximum is 134 // reached, the value will be converted to a string indicating that the max 135 // depth has been exceeded. If this field is not specified, a default 136 // value will be used. 137 MaxLogDepth int 138 } 139 140 // MessageClass indicates which category or categories of messages to consider. 141 type MessageClass int 142 143 const ( 144 // None ignores all message classes. 145 None MessageClass = iota 146 // All considers all message classes. 147 All 148 // Info only considers info messages. 149 Info 150 // Error only considers error messages. 151 Error 152 ) 153 154 // fnlogger inherits some of its LogSink implementation from Formatter 155 // and just needs to add some glue code. 156 type fnlogger struct { 157 Formatter 158 write func(prefix, args string) 159 } 160 161 func (l fnlogger) WithName(name string) logr.LogSink { 162 l.Formatter.AddName(name) 163 return &l 164 } 165 166 func (l fnlogger) WithValues(kvList ...interface{}) logr.LogSink { 167 l.Formatter.AddValues(kvList) 168 return &l 169 } 170 171 func (l fnlogger) WithCallDepth(depth int) logr.LogSink { 172 l.Formatter.AddCallDepth(depth) 173 return &l 174 } 175 176 func (l fnlogger) Info(level int, msg string, kvList ...interface{}) { 177 prefix, args := l.FormatInfo(level, msg, kvList) 178 l.write(prefix, args) 179 } 180 181 func (l fnlogger) Error(err error, msg string, kvList ...interface{}) { 182 prefix, args := l.FormatError(err, msg, kvList) 183 l.write(prefix, args) 184 } 185 186 func (l fnlogger) GetUnderlying() func(prefix, args string) { 187 return l.write 188 } 189 190 // Assert conformance to the interfaces. 191 var _ logr.LogSink = &fnlogger{} 192 var _ logr.CallDepthLogSink = &fnlogger{} 193 var _ Underlier = &fnlogger{} 194 195 // NewFormatter constructs a Formatter which emits a JSON-like key=value format. 196 func NewFormatter(opts Options) Formatter { 197 return newFormatter(opts, outputKeyValue) 198 } 199 200 // NewFormatterJSON constructs a Formatter which emits strict JSON. 201 func NewFormatterJSON(opts Options) Formatter { 202 return newFormatter(opts, outputJSON) 203 } 204 205 // Defaults for Options. 206 const defaultTimestampFormat = "2006-01-02 15:04:05.000000" 207 const defaultMaxLogDepth = 16 208 209 func newFormatter(opts Options, outfmt outputFormat) Formatter { 210 if opts.TimestampFormat == "" { 211 opts.TimestampFormat = defaultTimestampFormat 212 } 213 if opts.MaxLogDepth == 0 { 214 opts.MaxLogDepth = defaultMaxLogDepth 215 } 216 f := Formatter{ 217 outputFormat: outfmt, 218 prefix: "", 219 values: nil, 220 depth: 0, 221 opts: &opts, 222 } 223 return f 224 } 225 226 // Formatter is an opaque struct which can be embedded in a LogSink 227 // implementation. It should be constructed with NewFormatter. Some of 228 // its methods directly implement logr.LogSink. 229 type Formatter struct { 230 outputFormat outputFormat 231 prefix string 232 values []interface{} 233 valuesStr string 234 depth int 235 opts *Options 236 } 237 238 // outputFormat indicates which outputFormat to use. 239 type outputFormat int 240 241 const ( 242 // outputKeyValue emits a JSON-like key=value format, but not strict JSON. 243 outputKeyValue outputFormat = iota 244 // outputJSON emits strict JSON. 245 outputJSON 246 ) 247 248 // PseudoStruct is a list of key-value pairs that gets logged as a struct. 249 type PseudoStruct []interface{} 250 251 // render produces a log line, ready to use. 252 func (f Formatter) render(builtins, args []interface{}) string { 253 // Empirically bytes.Buffer is faster than strings.Builder for this. 254 buf := bytes.NewBuffer(make([]byte, 0, 1024)) 255 if f.outputFormat == outputJSON { 256 buf.WriteByte('{') 257 } 258 vals := builtins 259 if hook := f.opts.RenderBuiltinsHook; hook != nil { 260 vals = hook(f.sanitize(vals)) 261 } 262 f.flatten(buf, vals, false, false) // keys are ours, no need to escape 263 continuing := len(builtins) > 0 264 if len(f.valuesStr) > 0 { 265 if continuing { 266 if f.outputFormat == outputJSON { 267 buf.WriteByte(',') 268 } else { 269 buf.WriteByte(' ') 270 } 271 } 272 continuing = true 273 buf.WriteString(f.valuesStr) 274 } 275 vals = args 276 if hook := f.opts.RenderArgsHook; hook != nil { 277 vals = hook(f.sanitize(vals)) 278 } 279 f.flatten(buf, vals, continuing, true) // escape user-provided keys 280 if f.outputFormat == outputJSON { 281 buf.WriteByte('}') 282 } 283 return buf.String() 284 } 285 286 // flatten renders a list of key-value pairs into a buffer. If continuing is 287 // true, it assumes that the buffer has previous values and will emit a 288 // separator (which depends on the output format) before the first pair it 289 // writes. If escapeKeys is true, the keys are assumed to have 290 // non-JSON-compatible characters in them and must be evaluated for escapes. 291 // 292 // This function returns a potentially modified version of kvList, which 293 // ensures that there is a value for every key (adding a value if needed) and 294 // that each key is a string (substituting a key if needed). 295 func (f Formatter) flatten(buf *bytes.Buffer, kvList []interface{}, continuing bool, escapeKeys bool) []interface{} { 296 // This logic overlaps with sanitize() but saves one type-cast per key, 297 // which can be measurable. 298 if len(kvList)%2 != 0 { 299 kvList = append(kvList, noValue) 300 } 301 for i := 0; i < len(kvList); i += 2 { 302 k, ok := kvList[i].(string) 303 if !ok { 304 k = f.nonStringKey(kvList[i]) 305 kvList[i] = k 306 } 307 v := kvList[i+1] 308 309 if i > 0 || continuing { 310 if f.outputFormat == outputJSON { 311 buf.WriteByte(',') 312 } else { 313 // In theory the format could be something we don't understand. In 314 // practice, we control it, so it won't be. 315 buf.WriteByte(' ') 316 } 317 } 318 319 if escapeKeys { 320 buf.WriteString(prettyString(k)) 321 } else { 322 // this is faster 323 buf.WriteByte('"') 324 buf.WriteString(k) 325 buf.WriteByte('"') 326 } 327 if f.outputFormat == outputJSON { 328 buf.WriteByte(':') 329 } else { 330 buf.WriteByte('=') 331 } 332 buf.WriteString(f.pretty(v)) 333 } 334 return kvList 335 } 336 337 func (f Formatter) pretty(value interface{}) string { 338 return f.prettyWithFlags(value, 0, 0) 339 } 340 341 const ( 342 flagRawStruct = 0x1 // do not print braces on structs 343 ) 344 345 // TODO: This is not fast. Most of the overhead goes here. 346 func (f Formatter) prettyWithFlags(value interface{}, flags uint32, depth int) string { 347 if depth > f.opts.MaxLogDepth { 348 return `"<max-log-depth-exceeded>"` 349 } 350 351 // Handle types that take full control of logging. 352 if v, ok := value.(logr.Marshaler); ok { 353 // Replace the value with what the type wants to get logged. 354 // That then gets handled below via reflection. 355 value = invokeMarshaler(v) 356 } 357 358 // Handle types that want to format themselves. 359 switch v := value.(type) { 360 case fmt.Stringer: 361 value = invokeStringer(v) 362 case error: 363 value = invokeError(v) 364 } 365 366 // Handling the most common types without reflect is a small perf win. 367 switch v := value.(type) { 368 case bool: 369 return strconv.FormatBool(v) 370 case string: 371 return prettyString(v) 372 case int: 373 return strconv.FormatInt(int64(v), 10) 374 case int8: 375 return strconv.FormatInt(int64(v), 10) 376 case int16: 377 return strconv.FormatInt(int64(v), 10) 378 case int32: 379 return strconv.FormatInt(int64(v), 10) 380 case int64: 381 return strconv.FormatInt(int64(v), 10) 382 case uint: 383 return strconv.FormatUint(uint64(v), 10) 384 case uint8: 385 return strconv.FormatUint(uint64(v), 10) 386 case uint16: 387 return strconv.FormatUint(uint64(v), 10) 388 case uint32: 389 return strconv.FormatUint(uint64(v), 10) 390 case uint64: 391 return strconv.FormatUint(v, 10) 392 case uintptr: 393 return strconv.FormatUint(uint64(v), 10) 394 case float32: 395 return strconv.FormatFloat(float64(v), 'f', -1, 32) 396 case float64: 397 return strconv.FormatFloat(v, 'f', -1, 64) 398 case complex64: 399 return `"` + strconv.FormatComplex(complex128(v), 'f', -1, 64) + `"` 400 case complex128: 401 return `"` + strconv.FormatComplex(v, 'f', -1, 128) + `"` 402 case PseudoStruct: 403 buf := bytes.NewBuffer(make([]byte, 0, 1024)) 404 v = f.sanitize(v) 405 if flags&flagRawStruct == 0 { 406 buf.WriteByte('{') 407 } 408 for i := 0; i < len(v); i += 2 { 409 if i > 0 { 410 buf.WriteByte(',') 411 } 412 k, _ := v[i].(string) // sanitize() above means no need to check success 413 // arbitrary keys might need escaping 414 buf.WriteString(prettyString(k)) 415 buf.WriteByte(':') 416 buf.WriteString(f.prettyWithFlags(v[i+1], 0, depth+1)) 417 } 418 if flags&flagRawStruct == 0 { 419 buf.WriteByte('}') 420 } 421 return buf.String() 422 } 423 424 buf := bytes.NewBuffer(make([]byte, 0, 256)) 425 t := reflect.TypeOf(value) 426 if t == nil { 427 return "null" 428 } 429 v := reflect.ValueOf(value) 430 switch t.Kind() { 431 case reflect.Bool: 432 return strconv.FormatBool(v.Bool()) 433 case reflect.String: 434 return prettyString(v.String()) 435 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 436 return strconv.FormatInt(int64(v.Int()), 10) 437 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 438 return strconv.FormatUint(uint64(v.Uint()), 10) 439 case reflect.Float32: 440 return strconv.FormatFloat(float64(v.Float()), 'f', -1, 32) 441 case reflect.Float64: 442 return strconv.FormatFloat(v.Float(), 'f', -1, 64) 443 case reflect.Complex64: 444 return `"` + strconv.FormatComplex(complex128(v.Complex()), 'f', -1, 64) + `"` 445 case reflect.Complex128: 446 return `"` + strconv.FormatComplex(v.Complex(), 'f', -1, 128) + `"` 447 case reflect.Struct: 448 if flags&flagRawStruct == 0 { 449 buf.WriteByte('{') 450 } 451 printComma := false // testing i>0 is not enough because of JSON omitted fields 452 for i := 0; i < t.NumField(); i++ { 453 fld := t.Field(i) 454 if fld.PkgPath != "" { 455 // reflect says this field is only defined for non-exported fields. 456 continue 457 } 458 if !v.Field(i).CanInterface() { 459 // reflect isn't clear exactly what this means, but we can't use it. 460 continue 461 } 462 name := "" 463 omitempty := false 464 if tag, found := fld.Tag.Lookup("json"); found { 465 if tag == "-" { 466 continue 467 } 468 if comma := strings.Index(tag, ","); comma != -1 { 469 if n := tag[:comma]; n != "" { 470 name = n 471 } 472 rest := tag[comma:] 473 if strings.Contains(rest, ",omitempty,") || strings.HasSuffix(rest, ",omitempty") { 474 omitempty = true 475 } 476 } else { 477 name = tag 478 } 479 } 480 if omitempty && isEmpty(v.Field(i)) { 481 continue 482 } 483 if printComma { 484 buf.WriteByte(',') 485 } 486 printComma = true // if we got here, we are rendering a field 487 if fld.Anonymous && fld.Type.Kind() == reflect.Struct && name == "" { 488 buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), flags|flagRawStruct, depth+1)) 489 continue 490 } 491 if name == "" { 492 name = fld.Name 493 } 494 // field names can't contain characters which need escaping 495 buf.WriteByte('"') 496 buf.WriteString(name) 497 buf.WriteByte('"') 498 buf.WriteByte(':') 499 buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), 0, depth+1)) 500 } 501 if flags&flagRawStruct == 0 { 502 buf.WriteByte('}') 503 } 504 return buf.String() 505 case reflect.Slice, reflect.Array: 506 // If this is outputing as JSON make sure this isn't really a json.RawMessage. 507 // If so just emit "as-is" and don't pretty it as that will just print 508 // it as [X,Y,Z,...] which isn't terribly useful vs the string form you really want. 509 if f.outputFormat == outputJSON { 510 if rm, ok := value.(json.RawMessage); ok { 511 // If it's empty make sure we emit an empty value as the array style would below. 512 if len(rm) > 0 { 513 buf.Write(rm) 514 } else { 515 buf.WriteString("null") 516 } 517 return buf.String() 518 } 519 } 520 buf.WriteByte('[') 521 for i := 0; i < v.Len(); i++ { 522 if i > 0 { 523 buf.WriteByte(',') 524 } 525 e := v.Index(i) 526 buf.WriteString(f.prettyWithFlags(e.Interface(), 0, depth+1)) 527 } 528 buf.WriteByte(']') 529 return buf.String() 530 case reflect.Map: 531 buf.WriteByte('{') 532 // This does not sort the map keys, for best perf. 533 it := v.MapRange() 534 i := 0 535 for it.Next() { 536 if i > 0 { 537 buf.WriteByte(',') 538 } 539 // If a map key supports TextMarshaler, use it. 540 keystr := "" 541 if m, ok := it.Key().Interface().(encoding.TextMarshaler); ok { 542 txt, err := m.MarshalText() 543 if err != nil { 544 keystr = fmt.Sprintf("<error-MarshalText: %s>", err.Error()) 545 } else { 546 keystr = string(txt) 547 } 548 keystr = prettyString(keystr) 549 } else { 550 // prettyWithFlags will produce already-escaped values 551 keystr = f.prettyWithFlags(it.Key().Interface(), 0, depth+1) 552 if t.Key().Kind() != reflect.String { 553 // JSON only does string keys. Unlike Go's standard JSON, we'll 554 // convert just about anything to a string. 555 keystr = prettyString(keystr) 556 } 557 } 558 buf.WriteString(keystr) 559 buf.WriteByte(':') 560 buf.WriteString(f.prettyWithFlags(it.Value().Interface(), 0, depth+1)) 561 i++ 562 } 563 buf.WriteByte('}') 564 return buf.String() 565 case reflect.Ptr, reflect.Interface: 566 if v.IsNil() { 567 return "null" 568 } 569 return f.prettyWithFlags(v.Elem().Interface(), 0, depth) 570 } 571 return fmt.Sprintf(`"<unhandled-%s>"`, t.Kind().String()) 572 } 573 574 func prettyString(s string) string { 575 // Avoid escaping (which does allocations) if we can. 576 if needsEscape(s) { 577 return strconv.Quote(s) 578 } 579 b := bytes.NewBuffer(make([]byte, 0, 1024)) 580 b.WriteByte('"') 581 b.WriteString(s) 582 b.WriteByte('"') 583 return b.String() 584 } 585 586 // needsEscape determines whether the input string needs to be escaped or not, 587 // without doing any allocations. 588 func needsEscape(s string) bool { 589 for _, r := range s { 590 if !strconv.IsPrint(r) || r == '\\' || r == '"' { 591 return true 592 } 593 } 594 return false 595 } 596 597 func isEmpty(v reflect.Value) bool { 598 switch v.Kind() { 599 case reflect.Array, reflect.Map, reflect.Slice, reflect.String: 600 return v.Len() == 0 601 case reflect.Bool: 602 return !v.Bool() 603 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 604 return v.Int() == 0 605 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 606 return v.Uint() == 0 607 case reflect.Float32, reflect.Float64: 608 return v.Float() == 0 609 case reflect.Complex64, reflect.Complex128: 610 return v.Complex() == 0 611 case reflect.Interface, reflect.Ptr: 612 return v.IsNil() 613 } 614 return false 615 } 616 617 func invokeMarshaler(m logr.Marshaler) (ret interface{}) { 618 defer func() { 619 if r := recover(); r != nil { 620 ret = fmt.Sprintf("<panic: %s>", r) 621 } 622 }() 623 return m.MarshalLog() 624 } 625 626 func invokeStringer(s fmt.Stringer) (ret string) { 627 defer func() { 628 if r := recover(); r != nil { 629 ret = fmt.Sprintf("<panic: %s>", r) 630 } 631 }() 632 return s.String() 633 } 634 635 func invokeError(e error) (ret string) { 636 defer func() { 637 if r := recover(); r != nil { 638 ret = fmt.Sprintf("<panic: %s>", r) 639 } 640 }() 641 return e.Error() 642 } 643 644 // Caller represents the original call site for a log line, after considering 645 // logr.Logger.WithCallDepth and logr.Logger.WithCallStackHelper. The File and 646 // Line fields will always be provided, while the Func field is optional. 647 // Users can set the render hook fields in Options to examine logged key-value 648 // pairs, one of which will be {"caller", Caller} if the Options.LogCaller 649 // field is enabled for the given MessageClass. 650 type Caller struct { 651 // File is the basename of the file for this call site. 652 File string `json:"file"` 653 // Line is the line number in the file for this call site. 654 Line int `json:"line"` 655 // Func is the function name for this call site, or empty if 656 // Options.LogCallerFunc is not enabled. 657 Func string `json:"function,omitempty"` 658 } 659 660 func (f Formatter) caller() Caller { 661 // +1 for this frame, +1 for Info/Error. 662 pc, file, line, ok := runtime.Caller(f.depth + 2) 663 if !ok { 664 return Caller{"<unknown>", 0, ""} 665 } 666 fn := "" 667 if f.opts.LogCallerFunc { 668 if fp := runtime.FuncForPC(pc); fp != nil { 669 fn = fp.Name() 670 } 671 } 672 673 return Caller{filepath.Base(file), line, fn} 674 } 675 676 const noValue = "<no-value>" 677 678 func (f Formatter) nonStringKey(v interface{}) string { 679 return fmt.Sprintf("<non-string-key: %s>", f.snippet(v)) 680 } 681 682 // snippet produces a short snippet string of an arbitrary value. 683 func (f Formatter) snippet(v interface{}) string { 684 const snipLen = 16 685 686 snip := f.pretty(v) 687 if len(snip) > snipLen { 688 snip = snip[:snipLen] 689 } 690 return snip 691 } 692 693 // sanitize ensures that a list of key-value pairs has a value for every key 694 // (adding a value if needed) and that each key is a string (substituting a key 695 // if needed). 696 func (f Formatter) sanitize(kvList []interface{}) []interface{} { 697 if len(kvList)%2 != 0 { 698 kvList = append(kvList, noValue) 699 } 700 for i := 0; i < len(kvList); i += 2 { 701 _, ok := kvList[i].(string) 702 if !ok { 703 kvList[i] = f.nonStringKey(kvList[i]) 704 } 705 } 706 return kvList 707 } 708 709 // Init configures this Formatter from runtime info, such as the call depth 710 // imposed by logr itself. 711 // Note that this receiver is a pointer, so depth can be saved. 712 func (f *Formatter) Init(info logr.RuntimeInfo) { 713 f.depth += info.CallDepth 714 } 715 716 // Enabled checks whether an info message at the given level should be logged. 717 func (f Formatter) Enabled(level int) bool { 718 return level <= f.opts.Verbosity 719 } 720 721 // GetDepth returns the current depth of this Formatter. This is useful for 722 // implementations which do their own caller attribution. 723 func (f Formatter) GetDepth() int { 724 return f.depth 725 } 726 727 // FormatInfo renders an Info log message into strings. The prefix will be 728 // empty when no names were set (via AddNames), or when the output is 729 // configured for JSON. 730 func (f Formatter) FormatInfo(level int, msg string, kvList []interface{}) (prefix, argsStr string) { 731 args := make([]interface{}, 0, 64) // using a constant here impacts perf 732 prefix = f.prefix 733 if f.outputFormat == outputJSON { 734 args = append(args, "logger", prefix) 735 prefix = "" 736 } 737 if f.opts.LogTimestamp { 738 args = append(args, "ts", time.Now().Format(f.opts.TimestampFormat)) 739 } 740 if policy := f.opts.LogCaller; policy == All || policy == Info { 741 args = append(args, "caller", f.caller()) 742 } 743 args = append(args, "level", level, "msg", msg) 744 return prefix, f.render(args, kvList) 745 } 746 747 // FormatError renders an Error log message into strings. The prefix will be 748 // empty when no names were set (via AddNames), or when the output is 749 // configured for JSON. 750 func (f Formatter) FormatError(err error, msg string, kvList []interface{}) (prefix, argsStr string) { 751 args := make([]interface{}, 0, 64) // using a constant here impacts perf 752 prefix = f.prefix 753 if f.outputFormat == outputJSON { 754 args = append(args, "logger", prefix) 755 prefix = "" 756 } 757 if f.opts.LogTimestamp { 758 args = append(args, "ts", time.Now().Format(f.opts.TimestampFormat)) 759 } 760 if policy := f.opts.LogCaller; policy == All || policy == Error { 761 args = append(args, "caller", f.caller()) 762 } 763 args = append(args, "msg", msg) 764 var loggableErr interface{} 765 if err != nil { 766 loggableErr = err.Error() 767 } 768 args = append(args, "error", loggableErr) 769 return f.prefix, f.render(args, kvList) 770 } 771 772 // AddName appends the specified name. funcr uses '/' characters to separate 773 // name elements. Callers should not pass '/' in the provided name string, but 774 // this library does not actually enforce that. 775 func (f *Formatter) AddName(name string) { 776 if len(f.prefix) > 0 { 777 f.prefix += "/" 778 } 779 f.prefix += name 780 } 781 782 // AddValues adds key-value pairs to the set of saved values to be logged with 783 // each log line. 784 func (f *Formatter) AddValues(kvList []interface{}) { 785 // Three slice args forces a copy. 786 n := len(f.values) 787 f.values = append(f.values[:n:n], kvList...) 788 789 vals := f.values 790 if hook := f.opts.RenderValuesHook; hook != nil { 791 vals = hook(f.sanitize(vals)) 792 } 793 794 // Pre-render values, so we don't have to do it on each Info/Error call. 795 buf := bytes.NewBuffer(make([]byte, 0, 1024)) 796 f.flatten(buf, vals, false, true) // escape user-provided keys 797 f.valuesStr = buf.String() 798 } 799 800 // AddCallDepth increases the number of stack-frames to skip when attributing 801 // the log line to a file and line. 802 func (f *Formatter) AddCallDepth(depth int) { 803 f.depth += depth 804 }