Skip to content

Commit

Permalink
funcr: Add a Callback type which defers arg eval
Browse files Browse the repository at this point in the history
This can be used to defer expensive argument processing (large strings,
allocations, etc) when they might not be logged, such as high V levels.
When they ARE logged, the `Callback` is executed and the result value
(which is itself evaluated for Stringer and so on) is logged.
  • Loading branch information
thockin committed Dec 23, 2021
1 parent 99e02a9 commit 7e2e7dd
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 14 deletions.
39 changes: 25 additions & 14 deletions funcr/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package funcr_test

import (
"fmt"
"strings"

"github.com/go-logr/logr"
"github.com/go-logr/logr/funcr"
Expand Down Expand Up @@ -57,6 +58,28 @@ func ExampleUnderlier() {
// Output: hello world
}

func ExamplePseudoStruct() {
var log logr.Logger = funcr.NewJSON(
func(obj string) { fmt.Println(obj) },
funcr.Options{})
kv := []interface{}{
"field1", 12345,
"field2", true,
}
log.Info("the message", "key", funcr.PseudoStruct(kv))
// Output: {"logger":"","level":0,"msg":"the message","key":{"field1":12345,"field2":true}}
}

func ExampleCallback() {
var log logr.Logger = funcr.NewJSON(func(obj string) {
fmt.Println(obj)
}, funcr.Options{})
long := "I have of late but wherefore I know not lost all my mirth"

log.Info("the message", "key", funcr.Callback(func() interface{} { return strings.Fields(long)[5:9] }))
// Output: {"logger":"","level":0,"msg":"the message","key":["wherefore","I","know","not"]}
}

func ExampleOptions() {
var log logr.Logger = funcr.NewJSON(
func(obj string) { fmt.Println(obj) },
Expand All @@ -68,8 +91,8 @@ func ExampleOptions() {
log.V(1).Info("V(1) message", "key", "value")
log.V(2).Info("V(2) message", "key", "value")
// Output:
// {"logger":"","caller":{"file":"example_test.go","line":67},"level":0,"msg":"V(0) message","key":"value"}
// {"logger":"","caller":{"file":"example_test.go","line":68},"level":1,"msg":"V(1) message","key":"value"}
// {"logger":"","caller":{"file":"example_test.go","line":90},"level":0,"msg":"V(0) message","key":"value"}
// {"logger":"","caller":{"file":"example_test.go","line":91},"level":1,"msg":"V(1) message","key":"value"}
}

func ExampleOptions_renderHooks() {
Expand Down Expand Up @@ -100,18 +123,6 @@ func ExampleOptions_renderHooks() {
// Output: {"log:logger":"MyLogger","log:level":0,"log:msg":"the message","labels":{"savedKey1":"savedVal1","savedKey2":"savedVal2"},"key":"value"}
}

func ExamplePseudoStruct() {
var log logr.Logger = funcr.NewJSON(
func(obj string) { fmt.Println(obj) },
funcr.Options{})
kv := []interface{}{
"field1", 12345,
"field2", true,
}
log.Info("the message", "key", funcr.PseudoStruct(kv))
// Output: {"logger":"","level":0,"msg":"the message","key":{"field1":12345,"field2":true}}
}

func ExampleOptions_maxLogDepth() {
type List struct {
Next *List
Expand Down
9 changes: 9 additions & 0 deletions funcr/funcr.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,11 @@ const (
// PseudoStruct is a list of key-value pairs that gets logged as a struct.
type PseudoStruct []interface{}

// Callback is a function which gets called only when it is actually logged.
// This can be used to defer expensive operations when they might not be logged
// (for example at higher V levels).
type Callback func() interface{}

// render produces a log line, ready to use.
func (f Formatter) render(builtins, args []interface{}) string {
// Empirically bytes.Buffer is faster than strings.Builder for this.
Expand Down Expand Up @@ -352,6 +357,10 @@ func (f Formatter) prettyWithFlags(value interface{}, flags uint32, depth int) s
// Replace the value with what the type wants to get logged.
// That then gets handled below via reflection.
value = v.MarshalLog()
} else if v, ok := value.(Callback); ok {
// Replace the value with what the callsite wants to get logged.
// That then gets handled below via reflection.
value = v()
}

// Handle types that want to format themselves.
Expand Down
4 changes: 4 additions & 0 deletions funcr/funcr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,10 @@ func TestPretty(t *testing.T) {
},
exp: `{"[{\"S\":\"\\\"quoted\\\"\"},{\"S\":\"unquoted\"}]":1}`,
},
{
val: Callback(func() interface{} { return []int{8, 6, 7, 5, 3, 0, 9} }),
exp: `[8,6,7,5,3,0,9]`,
},
}

f := NewFormatter(Options{})
Expand Down

0 comments on commit 7e2e7dd

Please sign in to comment.