1
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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
53 func New(fn func(prefix, args string), opts Options) logr.Logger {
54 return logr.New(newSink(fn, NewFormatter(opts)))
55 }
56
57
58
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
67
68
69
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
80 l.Formatter.AddCallDepth(1)
81 return l
82 }
83
84
85 type Options struct {
86
87
88 LogCaller MessageClass
89
90
91
92 LogCallerFunc bool
93
94
95
96 LogTimestamp bool
97
98
99
100
101 TimestampFormat string
102
103
104
105
106 LogInfoLevel *string
107
108
109
110
111 Verbosity int
112
113
114
115
116
117
118
119
120
121
122
123
124 RenderBuiltinsHook func(kvList []any) []any
125
126
127
128
129 RenderValuesHook func(kvList []any) []any
130
131
132
133
134 RenderArgsHook func(kvList []any) []any
135
136
137
138
139
140
141
142 MaxLogDepth int
143 }
144
145
146 type MessageClass int
147
148 const (
149
150 None MessageClass = iota
151
152 All
153
154 Info
155
156 Error
157 )
158
159
160
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
196 var _ logr.LogSink = &fnlogger{}
197 var _ logr.CallDepthLogSink = &fnlogger{}
198 var _ Underlier = &fnlogger{}
199
200
201 func NewFormatter(opts Options) Formatter {
202 return newFormatter(opts, outputKeyValue)
203 }
204
205
206 func NewFormatterJSON(opts Options) Formatter {
207 return newFormatter(opts, outputJSON)
208 }
209
210
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
236
237
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
246 groups []groupDef
247 }
248
249
250 type outputFormat int
251
252 const (
253
254 outputKeyValue outputFormat = iota
255
256 outputJSON
257 )
258
259
260
261 type groupDef struct {
262 name string
263 values string
264 }
265
266
267 type PseudoStruct []any
268
269
270 func (f Formatter) render(builtins, args []any) string {
271
272 buf := bytes.NewBuffer(make([]byte, 0, 1024))
273
274 if f.outputFormat == outputJSON {
275 buf.WriteByte('{')
276 }
277
278
279 vals := builtins
280 if hook := f.opts.RenderBuiltinsHook; hook != nil {
281 vals = hook(f.sanitize(vals))
282 }
283 f.flatten(buf, vals, false)
284 continuing := len(builtins) > 0
285
286
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)
295
296 return buf.String()
297 }()
298
299
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
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('}')
319 }
320
321 return buf.String()
322 }
323
324
325
326
327
328
329
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))
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
362
363
364
365
366
367
368 func (f Formatter) flatten(buf *bytes.Buffer, kvList []any, escapeKeys bool) []any {
369
370
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
394
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
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
434 )
435
436
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
443 if v, ok := value.(logr.Marshaler); ok {
444
445
446 value = invokeMarshaler(v)
447 }
448
449
450 switch v := value.(type) {
451 case fmt.Stringer:
452 value = invokeStringer(v)
453 case error:
454 value = invokeError(v)
455 }
456
457
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)
504
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
543 for i := 0; i < t.NumField(); i++ {
544 fld := t.Field(i)
545 if fld.PkgPath != "" {
546
547 continue
548 }
549 if !v.Field(i).CanInterface() {
550
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
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
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
596
597
598 if f.outputFormat == outputJSON {
599 if rm, ok := value.(json.RawMessage); ok {
600
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
622 it := v.MapRange()
623 i := 0
624 for it.Next() {
625 if i > 0 {
626 buf.WriteByte(f.comma())
627 }
628
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
640 keystr = f.prettyWithFlags(it.Key().Interface(), 0, depth+1)
641 if t.Key().Kind() != reflect.String {
642
643
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
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
676
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
734
735
736
737
738
739 type Caller struct {
740
741 File string `json:"file"`
742
743 Line int `json:"line"`
744
745
746 Func string `json:"function,omitempty"`
747 }
748
749 func (f Formatter) caller() Caller {
750
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
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
783
784
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
799
800
801 func (f *Formatter) startGroup(name string) {
802
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
811 f.groupName = name
812 f.valuesStr = ""
813 f.values = nil
814 }
815
816
817
818
819 func (f *Formatter) Init(info logr.RuntimeInfo) {
820 f.depth += info.CallDepth
821 }
822
823
824 func (f Formatter) Enabled(level int) bool {
825 return level <= f.opts.Verbosity
826 }
827
828
829
830 func (f Formatter) GetDepth() int {
831 return f.depth
832 }
833
834
835
836
837 func (f Formatter) FormatInfo(level int, msg string, kvList []any) (prefix, argsStr string) {
838 args := make([]any, 0, 64)
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
858
859
860 func (f Formatter) FormatError(err error, msg string, kvList []any) (prefix, argsStr string) {
861 args := make([]any, 0, 64)
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
883
884
885 func (f *Formatter) AddName(name string) {
886 if len(f.prefix) > 0 {
887 f.prefix += "/"
888 }
889 f.prefix += name
890 }
891
892
893
894 func (f *Formatter) AddValues(kvList []any) {
895
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
905 buf := bytes.NewBuffer(make([]byte, 0, 1024))
906 f.flatten(buf, vals, true)
907 f.valuesStr = buf.String()
908 }
909
910
911
912 func (f *Formatter) AddCallDepth(depth int) {
913 f.depth += depth
914 }
915
View as plain text