Repository: rs/zerolog
Pull request #337 (2021-07-30 13:17:56)

Add logr implementation to README and json.RawMessage to Fields()
Add the fastest (fewest allocation) [logr](https://github.com/go-logr/logr) implementation using zerolog, much faster than zapr. Use type switch instead Fields() to avoid map overhead (even with sync.Pool).

Benchmark compared to other logr implementations
```
go version go1.16.6 linux/amd64
cpu: Intel(R) Core(TM) i7-4870HQ CPU @ 2.50GHz

BenchmarkDiscardInfoOneArg-8             1247884               953.4 ns/op           176 B/op          4 allocs/op
BenchmarkDiscardInfoSeveralArgs-8         721035              1484 ns/op             336 B/op          6 allocs/op
BenchmarkDiscardV0Info-8                  770527              1486 ns/op             336 B/op          6 allocs/op
BenchmarkDiscardV9Info-8                 9779283               137.8 ns/op           176 B/op          2 allocs/op
BenchmarkDiscardError-8                   677576              1542 ns/op             336 B/op          6 allocs/op
BenchmarkDiscardWithValues-8             5563468               225.2 ns/op           192 B/op          3 allocs/op
BenchmarkDiscardWithName-8              20706103                58.52 ns/op           64 B/op          1 allocs/op
BenchmarkFuncrInfoOneArg-8               1255844               948.2 ns/op           176 B/op          4 allocs/op
BenchmarkFuncrInfoSeveralArgs-8           802514              1491 ns/op             336 B/op          6 allocs/op
BenchmarkFuncrV0Info-8                    795092              1480 ns/op             336 B/op          6 allocs/op
BenchmarkFuncrV9Info-8                   9467601               140.3 ns/op           176 B/op          2 allocs/op
BenchmarkFuncrError-8                     791719              1528 ns/op             336 B/op          6 allocs/op
BenchmarkFuncrWithValues-8               5428614               222.8 ns/op           192 B/op          3 allocs/op
BenchmarkFuncrWithName-8                18420804                62.07 ns/op           64 B/op          1 allocs/op
```
stdr: https://github.com/go-logr/stdr/pull/14
```
BenchmarkDiscardInfoOneArg-8             1319188               892.8 ns/op           232 B/op          8 allocs/op
BenchmarkDiscardInfoSeveralArgs-8         742125              1580 ns/op             552 B/op         14 allocs/op
BenchmarkDiscardV0Info-8                  755325              1621 ns/op             552 B/op         14 allocs/op
BenchmarkDiscardV9Info-8                 8628398               133.5 ns/op           176 B/op          2 allocs/op
BenchmarkDiscardError-8                   693415              1715 ns/op             600 B/op         16 allocs/op
BenchmarkDiscardWithValues-8            n 4856173              242.6 ns/op           208 B/op          3 allocs/op
BenchmarkDiscardWithName-8              12319874                85.29 ns/op           80 B/op          1 allocs/op
BenchmarkFuncrInfoOneArg-8               1332094               904.2 ns/op           232 B/op          8 allocs/op
BenchmarkFuncrInfoSeveralArgs-8           689158              1561 ns/op             552 B/op         14 allocs/op
BenchmarkFuncrV0Info-8                    769890              1629 ns/op             552 B/op         14 allocs/op
BenchmarkFuncrV9Info-8                   8916390               138.0 ns/op           176 B/op          2 allocs/op
BenchmarkFuncrError-8                     693705              1723 ns/op             600 B/op         16 allocs/op
BenchmarkFuncrWithValues-8               4718301               251.3 ns/op           208 B/op          3 allocs/op
BenchmarkFuncrWithName-8                12068954                91.43 ns/op           80 B/op          1 allocs/op
```
glogr: https://github.com/go-logr/glogr/pull/16
```
BenchmarkDiscardInfoOneArg-8              464660              2199 ns/op             400 B/op          9 allocs/op
BenchmarkDiscardInfoSeveralArgs-8         411957              2908 ns/op             664 B/op         15 allocs/op
BenchmarkDiscardV0Info-8                  382228              2879 ns/op             664 B/op         15 allocs/op
BenchmarkDiscardV9Info-8                 9592717               138.8 ns/op           176 B/op          2 allocs/op
BenchmarkDiscardError-8                   327556              3160 ns/op             696 B/op         17 allocs/op
BenchmarkDiscardWithValues-8             5553832               223.2 ns/op           192 B/op          3 allocs/op
BenchmarkDiscardWithName-8              17091950                71.42 ns/op           64 B/op          1 allocs/op
BenchmarkFuncrInfoOneArg-8                546212              2196 ns/op             400 B/op          9 allocs/op
BenchmarkFuncrInfoSeveralArgs-8           396565              2890 ns/op             664 B/op         15 allocs/op
BenchmarkFuncrV0Info-8                    421482              2872 ns/op             664 B/op         15 allocs/op
BenchmarkFuncrV9Info-8                   8809033               138.8 ns/op           176 B/op          2 allocs/op
BenchmarkFuncrError-8                     383487              3121 ns/op             696 B/op         17 allocs/op
BenchmarkFuncrWithValues-8               5243806               221.5 ns/op           192 B/op          3 allocs/op
BenchmarkFuncrWithName-8                15284324                70.80 ns/op           64 B/op          1 allocs/op
```
zapr: https://github.com/go-logr/zapr/pull/33
```
BenchmarkDiscardInfoOneArg-8              273366              4181 ns/op             713 B/op         15 allocs/op
BenchmarkDiscardInfoSeveralArgs-8         227490              5202 ns/op            1130 B/op         17 allocs/op
BenchmarkDiscardV0Info-8                  238732              5162 ns/op            1129 B/op         17 allocs/op
BenchmarkDiscardV9Info-8                 9315973               141.0 ns/op           176 B/op          2 allocs/op
BenchmarkDiscardError-8                   192272              5543 ns/op            1258 B/op         18 allocs/op
BenchmarkDiscardWithValues-8              831561              1432 ns/op            1544 B/op         10 allocs/op
BenchmarkDiscardWithName-8               5448158               201.5 ns/op           216 B/op          4 allocs/op
BenchmarkFuncrInfoOneArg-8                261658              4174 ns/op             713 B/op         15 allocs/op
BenchmarkFuncrInfoSeveralArgs-8           227588              5172 ns/op            1130 B/op         17 allocs/op
BenchmarkFuncrV0Info-8                    227376              5158 ns/op            1130 B/op         17 allocs/op
BenchmarkFuncrV9Info-8                   8277498               147.4 ns/op           176 B/op          2 allocs/op
BenchmarkFuncrError-8                     205752              5576 ns/op            1258 B/op         18 allocs/op
BenchmarkFuncrWithValues-8                939271              1392 ns/op            1544 B/op         10 allocs/op
BenchmarkFuncrWithName-8                 5572585               208.5 ns/op           216 B/op          4 allocs/op
```

hn8: @rs Do you think it is ok to add "official" logr wrapper in zerolog repo for extra 200 LoC (excluding tests)? Zerolog+logr is being used in popular webrtc project pion/ion-sfu. Thanks
rs: What is the advantage of having this implementation inside zerolog instead of as a separate project like all other implementations of this interface? I'm generally against adding dependencies, unnecessary complexity or any code that could be implemented outside the library unless there is a good reason.

As I would not be the maintainer of this implementation, I believe it would be better living in a repository you own.
hn8: @rs No problem. The existence of log/journald/pkgerrors was my motivation to contribute to zerolog repo to having simple subpackages to integrate with other popular libraries. I was trying to optimize go-logr/zapr first, and it turns out zerlog is the fastest implementation for logr after some tuning. IMHO, many Go projects want to make their logging interface generic for library consumers. For structured logging, they could choose zapr (but turns out much slower) and reivent the wheel without best optimization in each project. For example, this is one project I get involved.
https://github.com/pion/ion-sfu/blob/master/pkg/logger/zerologr.go

I would love to help maintaining logr wrapper in zerolog if you allow me. But it doesn't make much sense for me to own a thin wrapper as a standalone Go package. If you really hate this idea, I would probably just copy and paste to projects using zerologr+logr since the code is just 200 LoC. Thanks for the great zerolog package!
hn8: @rs No problem. The existence of log/journald/pkgerrors was my motivation to contribute to zerolog repo to having simple subpackages to integrate with other popular libraries. I was trying to optimize go-logr/zapr first, and it turns out zerlog is the fastest implementation for logr after some tuning. IMHO, many Go projects want to keep their logging interface generic (like logr) for library consumers. For structured logging, library consumers could choose zapr (but turns out much slower) or reivent the wheel for zerlog without the best optimization in each project. For example, this is one project I get involved.
https://github.com/pion/ion-sfu/blob/master/pkg/logger/zerologr.go

I would love to help maintain logr wrapper in zerolog if you allow me. But it doesn't make much sense for me to own a thin wrapper between 2 popular Go packages (zerolog/logr both 4K+) as a standalone Go package (too npm-ish). It is even harder to convince go-logr to host zerologr (like other implementations above) since the maintainer is pro-glogr since zerologr is the fastest. If you dislike this idea, please kindly close the PR. Totally understand your concern. Probably just copy and paste to projects using zerolog+logr since the code is just 200 LoC. Thanks for the great zerolog package!
rs: To be honest, I should have refused journald and pkgerrors.

I'm all for pointing to other projects like logr wrapper in the README. I prefer people composing the right set of libraries for their needs rather than big libraries with hundreds of dependencies to fulfill all the possible needs they will probably never have :)
hn8: @rs I reverted logr subpackage and added to README instead as you suggested. Also revert type switch but only add json.RawMessage to Fields() as the minimal change for you to review. I really admire your work and hope that you change your mind one day on hosting logr wrapper since zerolog is definitely the best one. It is funny that neither zerolog nor logr is willing to host the zerolog-logr wrapper, but it is understandable. Just a little bit more hassle for developers like us to use a third-party like mine (I have zero intention for self-promotion). Thanks for your clear communication, appreciate your time and great work!

Patch:
diff --git a/README.md b/README.md
index 51e3e36..536e3e4 100644
--- a/README.md
+++ b/README.md
@@ -617,6 +617,7 @@ with zerolog library is [CSD](https://github.com/toravir/csd/).
 
 * [grpc-zerolog](https://github.com/cheapRoc/grpc-zerolog): Implementation of `grpclog.LoggerV2` interface using `zerolog`
 * [overlog](https://github.com/Trendyol/overlog): Implementation of `Mapped Diagnostic Context` interface using `zerolog`
+* [zerologr](https://github.com/hn8/zerologr): Implementation of `logr.LogSink` interface using `zerolog`
 
 ## Benchmarks
 
diff --git a/event.go b/event.go
index 6f8341c..5a6d2a3 100644
--- a/event.go
+++ b/event.go
@@ -207,6 +207,12 @@ func (e *Event) Object(key string, obj LogObjectMarshaler) *Event {
 		return e
 	}
 	e.buf = enc.AppendKey(e.buf, key)
+	if obj == nil {
+		e.buf = enc.AppendNil(e.buf)
+
+		return e
+	}
+
 	e.appendObject(obj)
 	return e
 }
@@ -224,6 +230,9 @@ func (e *Event) EmbedObject(obj LogObjectMarshaler) *Event {
 	if e == nil {
 		return e
 	}
+	if obj == nil {
+		return e
+	}
 	obj.MarshalZerologObject(e)
 	return e
 }
diff --git a/fields.go b/fields.go
index cf3c3e9..907b3f0 100644
--- a/fields.go
+++ b/fields.go
@@ -1,6 +1,7 @@
 package zerolog
 
 import (
+	"encoding/json"
 	"net"
 	"sort"
 	"time"
@@ -245,6 +246,8 @@ func appendFields(dst []byte, fields map[string]interface{}) []byte {
 			dst = enc.AppendIPPrefix(dst, val)
 		case net.HardwareAddr:
 			dst = enc.AppendMACAddr(dst, val)
+		case json.RawMessage:
+			dst = appendJSON(dst, val)
 		default:
 			dst = enc.AppendInterface(dst, val)
 		}


Test patch:
diff --git a/event_test.go b/event_test.go
index 879eca1..98041b3 100644
--- a/event_test.go
+++ b/event_test.go
@@ -37,3 +37,29 @@ func TestEvent_AnErr(t *testing.T) {
 		})
 	}
 }
+
+func TestEvent_ObjectWithNil(t *testing.T) {
+	var buf bytes.Buffer
+	e := newEvent(levelWriterAdapter{&buf}, DebugLevel)
+	_ = e.Object("obj", nil)
+	_ = e.write()
+
+	want := `{"obj":null}`
+	got := strings.TrimSpace(buf.String())
+	if got != want {
+		t.Errorf("Event.Object() = %q, want %q", got, want)
+	}
+}
+
+func TestEvent_EmbedObjectWithNil(t *testing.T) {
+	var buf bytes.Buffer
+	e := newEvent(levelWriterAdapter{&buf}, DebugLevel)
+	_ = e.EmbedObject(nil)
+	_ = e.write()
+
+	want := "{}"
+	got := strings.TrimSpace(buf.String())
+	if got != want {
+		t.Errorf("Event.EmbedObject() = %q, want %q", got, want)
+	}
+}