96 | 96 |
// more logs. Info logs at or below this level will be written, while logs
|
97 | 97 |
// above this level will be discarded.
|
98 | 98 |
Verbosity int
|
|
99 |
|
|
100 |
// RenderBuiltinsHook allows users to mutate the list of key-value pairs
|
|
101 |
// while a log line is being rendered. The kvList argument follows logr
|
|
102 |
// conventions - each pair of slice elements is comprised of a string key
|
|
103 |
// and an arbitrary value (verified and sanitized before calling this
|
|
104 |
// hook). The value returned must follow the same conventions. This hook
|
|
105 |
// can be used to audit or modify logged data. For example, you might want
|
|
106 |
// to prefix all of funcr's built-in keys with some string. This hook is
|
|
107 |
// only called for built-in (provided by funcr itself) key-value pairs.
|
|
108 |
// Equivalent hooks are offered for key-value pairs saved via
|
|
109 |
// logr.Logger.WithValues or Formatter.AddValues (see RenderValuesHook) and
|
|
110 |
// for user-provided pairs (see RenderArgsHook).
|
|
111 |
RenderBuiltinsHook func(kvList []interface{}) []interface{}
|
|
112 |
|
|
113 |
// RenderValuesHook is the same as RenderBuiltinsHook, except that it is
|
|
114 |
// only called for key-value pairs saved via logr.Logger.WithValues. See
|
|
115 |
// RenderBuiltinsHook for more details.
|
|
116 |
RenderValuesHook func(kvList []interface{}) []interface{}
|
|
117 |
|
|
118 |
// RenderArgsHook is the same as RenderBuiltinsHook, except that it is only
|
|
119 |
// called for key-value pairs passed directly to Info and Error. See
|
|
120 |
// RenderBuiltinsHook for more details.
|
|
121 |
RenderArgsHook func(kvList []interface{}) []interface{}
|
99 | 122 |
}
|
100 | 123 |
|
101 | 124 |
// MessageClass indicates which category or categories of messages to consider.
|
|
198 | 221 |
outputJSON
|
199 | 222 |
)
|
200 | 223 |
|
201 | |
// render produces a log-line, ready to use.
|
|
224 |
// PseudoStruct is a list of key-value pairs that gets logged as a struct.
|
|
225 |
type PseudoStruct []interface{}
|
|
226 |
|
|
227 |
// render produces a log line, ready to use.
|
202 | 228 |
func (f Formatter) render(builtins, args []interface{}) string {
|
203 | 229 |
// Empirically bytes.Buffer is faster than strings.Builder for this.
|
204 | 230 |
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
205 | 231 |
if f.outputFormat == outputJSON {
|
206 | 232 |
buf.WriteByte('{')
|
207 | 233 |
}
|
208 | |
f.flatten(buf, builtins, false)
|
|
234 |
vals := builtins
|
|
235 |
if hook := f.opts.RenderBuiltinsHook; hook != nil {
|
|
236 |
vals = hook(f.sanitize(vals))
|
|
237 |
}
|
|
238 |
f.flatten(buf, vals, false)
|
209 | 239 |
continuing := len(builtins) > 0
|
210 | 240 |
if len(f.valuesStr) > 0 {
|
211 | 241 |
if continuing {
|
|
218 | 248 |
continuing = true
|
219 | 249 |
buf.WriteString(f.valuesStr)
|
220 | 250 |
}
|
221 | |
f.flatten(buf, args, continuing)
|
|
251 |
vals = args
|
|
252 |
if hook := f.opts.RenderArgsHook; hook != nil {
|
|
253 |
vals = hook(f.sanitize(vals))
|
|
254 |
}
|
|
255 |
f.flatten(buf, vals, continuing)
|
222 | 256 |
if f.outputFormat == outputJSON {
|
223 | 257 |
buf.WriteByte('}')
|
224 | 258 |
}
|
|
232 | 266 |
// ensures that there is a value for every key (adding a value if needed) and
|
233 | 267 |
// that each key is a string (substituting a key if needed).
|
234 | 268 |
func (f Formatter) flatten(buf *bytes.Buffer, kvList []interface{}, continuing bool) []interface{} {
|
|
269 |
// This logic overlaps with sanitize() but saves one type-cast per key,
|
|
270 |
// which can be measurable.
|
235 | 271 |
if len(kvList)%2 != 0 {
|
236 | |
kvList = append(kvList, "<no-value>")
|
|
272 |
kvList = append(kvList, noValue)
|
237 | 273 |
}
|
238 | 274 |
for i := 0; i < len(kvList); i += 2 {
|
239 | 275 |
k, ok := kvList[i].(string)
|
240 | 276 |
if !ok {
|
241 | |
snippet := f.pretty(kvList[i])
|
242 | |
if len(snippet) > 16 {
|
243 | |
snippet = snippet[:16]
|
244 | |
}
|
245 | |
k = fmt.Sprintf("<non-string-key: %s>", snippet)
|
|
277 |
k = f.nonStringKey(kvList[i])
|
246 | 278 |
kvList[i] = k
|
247 | 279 |
}
|
248 | 280 |
v := kvList[i+1]
|
|
336 | 368 |
return `"` + strconv.FormatComplex(complex128(v), 'f', -1, 64) + `"`
|
337 | 369 |
case complex128:
|
338 | 370 |
return `"` + strconv.FormatComplex(v, 'f', -1, 128) + `"`
|
|
371 |
case PseudoStruct:
|
|
372 |
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
|
373 |
v = f.sanitize(v)
|
|
374 |
if flags&flagRawStruct == 0 {
|
|
375 |
buf.WriteByte('{')
|
|
376 |
}
|
|
377 |
for i := 0; i < len(v); i += 2 {
|
|
378 |
if i > 0 {
|
|
379 |
buf.WriteByte(',')
|
|
380 |
}
|
|
381 |
buf.WriteByte('"')
|
|
382 |
buf.WriteString(v[i].(string))
|
|
383 |
buf.WriteByte('"')
|
|
384 |
buf.WriteByte(':')
|
|
385 |
buf.WriteString(f.pretty(v[i+1]))
|
|
386 |
}
|
|
387 |
if flags&flagRawStruct == 0 {
|
|
388 |
buf.WriteByte('}')
|
|
389 |
}
|
|
390 |
return buf.String()
|
339 | 391 |
}
|
340 | 392 |
|
341 | 393 |
buf := bytes.NewBuffer(make([]byte, 0, 256))
|
|
479 | 531 |
return false
|
480 | 532 |
}
|
481 | 533 |
|
482 | |
type callerID struct {
|
|
534 |
// Caller represents the original call site for a log line, after considering
|
|
535 |
// logr.Logger.WithCallDepth and logr.Logger.WithCallStackHelper. The File and
|
|
536 |
// Line fields will always be provided, while the Func field is optional.
|
|
537 |
// Users can set the render hook fields in Options to examine logged key-value
|
|
538 |
// pairs, one of which will be {"caller", Caller} if the Options.LogCaller
|
|
539 |
// field is enabled for the given MessageClass.
|
|
540 |
type Caller struct {
|
|
541 |
// File is the basename of the file for this call site.
|
483 | 542 |
File string `json:"file"`
|
484 | |
Line int `json:"line"`
|
|
543 |
// Line is the line number in the file for this call site.
|
|
544 |
Line int `json:"line"`
|
|
545 |
// Func is the function name for this call site, or empty if
|
|
546 |
// Options.LogCallerFunc is not enabled.
|
485 | 547 |
Func string `json:"function,omitempty"`
|
486 | 548 |
}
|
487 | 549 |
|
488 | |
func (f Formatter) caller() callerID {
|
|
550 |
func (f Formatter) caller() Caller {
|
489 | 551 |
// +1 for this frame, +1 for Info/Error.
|
490 | 552 |
pc, file, line, ok := runtime.Caller(f.depth + 2)
|
491 | 553 |
if !ok {
|
492 | |
return callerID{"<unknown>", 0, ""}
|
|
554 |
return Caller{"<unknown>", 0, ""}
|
493 | 555 |
}
|
494 | 556 |
fn := ""
|
495 | 557 |
if f.opts.LogCallerFunc {
|
|
498 | 560 |
}
|
499 | 561 |
}
|
500 | 562 |
|
501 | |
return callerID{filepath.Base(file), line, fn}
|
|
563 |
return Caller{filepath.Base(file), line, fn}
|
|
564 |
}
|
|
565 |
|
|
566 |
const noValue = "<no-value>"
|
|
567 |
|
|
568 |
func (f Formatter) nonStringKey(v interface{}) string {
|
|
569 |
return fmt.Sprintf("<non-string-key: %s>", f.snippet(v))
|
|
570 |
}
|
|
571 |
|
|
572 |
// snippet produces a short snippet string of an arbitrary value.
|
|
573 |
func (f Formatter) snippet(v interface{}) string {
|
|
574 |
const snipLen = 16
|
|
575 |
|
|
576 |
snip := f.pretty(v)
|
|
577 |
if len(snip) > snipLen {
|
|
578 |
snip = snip[:snipLen]
|
|
579 |
}
|
|
580 |
return snip
|
|
581 |
}
|
|
582 |
|
|
583 |
// sanitize ensures that a list of key-value pairs has a value for every key
|
|
584 |
// (adding a value if needed) and that each key is a string (substituting a key
|
|
585 |
// if needed).
|
|
586 |
func (f Formatter) sanitize(kvList []interface{}) []interface{} {
|
|
587 |
if len(kvList)%2 != 0 {
|
|
588 |
kvList = append(kvList, noValue)
|
|
589 |
}
|
|
590 |
for i := 0; i < len(kvList); i += 2 {
|
|
591 |
_, ok := kvList[i].(string)
|
|
592 |
if !ok {
|
|
593 |
kvList[i] = f.nonStringKey(kvList[i])
|
|
594 |
}
|
|
595 |
}
|
|
596 |
return kvList
|
502 | 597 |
}
|
503 | 598 |
|
504 | 599 |
// Init configures this Formatter from runtime info, such as the call depth
|
|
577 | 672 |
// AddValues adds key-value pairs to the set of saved values to be logged with
|
578 | 673 |
// each log line.
|
579 | 674 |
func (f *Formatter) AddValues(kvList []interface{}) {
|
|
675 |
// Three slice args forces a copy.
|
|
676 |
n := len(f.values)
|
|
677 |
vals := append(f.values[:n:n], kvList...)
|
|
678 |
if hook := f.opts.RenderValuesHook; hook != nil {
|
|
679 |
vals = hook(f.sanitize(vals))
|
|
680 |
}
|
|
681 |
|
580 | 682 |
// Pre-render values, so we don't have to do it on each Info/Error call.
|
581 | 683 |
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
582 | |
// Three slice args forces a copy.
|
583 | |
n := len(f.values)
|
584 | |
f.values = f.flatten(buf, append(f.values[:n:n], kvList...), false)
|
|
684 |
f.values = f.flatten(buf, vals, false)
|
585 | 685 |
f.valuesStr = buf.String()
|
586 | 686 |
}
|
587 | 687 |
|