Repository: rs/zerolog
Pull request #321 (2021-05-19 20:06:09)

Add Func log method
This is adds the `Func` log method to log using an anonymous function
only if the level is currently enabled.

The use case is for when you don't own an object and therefore can't
create your own marshaller but need to do some translation prior to
logging.

For example, this:

```go
msg := log.Debug()
if msg.Enabled() {
  msg.Str("complicated_thing", makeBinaryThingLoggable(thing))
}
msg.Msg("Sending complicated thing")
```

Turns into this:

```go
log.Debug().
  Func(func(e *Event) { e.Str("complicated_thing", makeBinaryThingLoggable(thing)) }).
  Msg("Sending complicated thing")
```

I considered a `StrFunc` instead which would be simpler but less flexible. I'm open to either and the possibility I'm missing a much simpler way to accomplish the same thing.

rs: Would be nice to add a usage example in the readme next to the other method.
marwatk: > Would be nice to add a usage example in the readme next to the other method.

@rs, I'm not 100% sure where you mean. No sections in the readme are jumping out at me as a logical place to put it. Do you have a specific section in mind?
rs: You're right, I thought the `Enable` example was in its own section.

Patch:
diff --git a/README.md b/README.md
index 36ed93b..51e3e36 100644
--- a/README.md
+++ b/README.md
@@ -591,6 +591,7 @@ Some settings can be changed and will by applied to all loggers:
 ### Advanced Fields
 
 * `Err`: Takes an `error` and renders it as a string using the `zerolog.ErrorFieldName` field name.
+* `Func`: Run a `func` only if the level is enabled.
 * `Timestamp`: Inserts a timestamp field with `zerolog.TimestampFieldName` field name, formatted using `zerolog.TimeFieldFormat`.
 * `Time`: Adds a field with time formatted with `zerolog.TimeFieldFormat`.
 * `Dur`: Adds a field with `time.Duration`.
diff --git a/event.go b/event.go
index ff6ff24..51ea4bb 100644
--- a/event.go
+++ b/event.go
@@ -211,6 +211,14 @@ func (e *Event) Object(key string, obj LogObjectMarshaler) *Event {
 	return e
 }
 
+// Func allows an anonymous func to run only if the event is enabled.
+func (e *Event) Func(f func(e *Event)) *Event {
+	if e != nil && e.Enabled() {
+		f(e)
+	}
+	return e
+}
+
 // EmbedObject marshals an object that implement the LogObjectMarshaler interface.
 func (e *Event) EmbedObject(obj LogObjectMarshaler) *Event {
 	if e == nil {


Test patch:
diff --git a/log_test.go b/log_test.go
index 82bcf8b..2d3d8fc 100644
--- a/log_test.go
+++ b/log_test.go
@@ -260,6 +260,7 @@ func TestFields(t *testing.T) {
 		Bytes("bytes", []byte("bar")).
 		Hex("hex", []byte{0x12, 0xef}).
 		RawJSON("json", []byte(`{"some":"json"}`)).
+		Func(func(e *Event) { e.Str("func", "func_output") }).
 		AnErr("some_err", nil).
 		Err(errors.New("some error")).
 		Bool("bool", true).
@@ -283,7 +284,7 @@ func TestFields(t *testing.T) {
 		Time("time", time.Time{}).
 		TimeDiff("diff", now, now.Add(-10*time.Second)).
 		Msg("")
-	if got, want := decodeIfBinaryToString(out.Bytes()), `{"caller":"`+caller+`","string":"foo","stringer":"127.0.0.1","stringer_nil":null,"bytes":"bar","hex":"12ef","json":{"some":"json"},"error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"IPv4":"192.168.0.100","IPv6":"2001:db8:85a3::8a2e:370:7334","Mac":"00:14:22:01:23:45","Prefix":"192.168.0.100/24","float32":11.1234,"float64":12.321321321,"dur":1000,"time":"0001-01-01T00:00:00Z","diff":10000}`+"\n"; got != want {
+	if got, want := decodeIfBinaryToString(out.Bytes()), `{"caller":"`+caller+`","string":"foo","stringer":"127.0.0.1","stringer_nil":null,"bytes":"bar","hex":"12ef","json":{"some":"json"},"func":"func_output","error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"IPv4":"192.168.0.100","IPv6":"2001:db8:85a3::8a2e:370:7334","Mac":"00:14:22:01:23:45","Prefix":"192.168.0.100/24","float32":11.1234,"float64":12.321321321,"dur":1000,"time":"0001-01-01T00:00:00Z","diff":10000}`+"\n"; got != want {
 		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
 	}
 }
@@ -379,6 +380,7 @@ func TestFieldsDisabled(t *testing.T) {
 		Hex("hex", []byte{0x12, 0xef}).
 		AnErr("some_err", nil).
 		Err(errors.New("some error")).
+		Func(func(e *Event) { e.Str("func", "func_output") }).
 		Bool("bool", true).
 		Int("int", 1).
 		Int8("int8", 2).