From a01e9a220589e9122aebdf3dee4b9a5abd344554 Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Mon, 20 Sep 2021 10:24:54 +0200 Subject: [PATCH] support logr.Marshaler This is useful for objects where zap would prefer to log as string or when reflection doesn't pick up the desired fields (unexported or too many). --- example/main.go | 3 +++ example_test.go | 4 +++ go.mod | 2 +- go.sum | 18 ++----------- internal/types/objectref.go | 50 +++++++++++++++++++++++++++++++++++++ zapr.go | 17 ++++++++++--- 6 files changed, 73 insertions(+), 21 deletions(-) create mode 100644 internal/types/objectref.go diff --git a/example/main.go b/example/main.go index d598c2d..31eb1f3 100644 --- a/example/main.go +++ b/example/main.go @@ -19,6 +19,7 @@ package main import ( "github.com/go-logr/logr" "github.com/go-logr/zapr" + "github.com/go-logr/zapr/internal/types" "go.uber.org/zap" ) @@ -52,6 +53,8 @@ func main() { // abstraction. Even that part is written so that it works with non-zap // loggers. func example(log logr.Logger) { + v := types.ObjectRef{Name: "myname", Namespace: "myns"} + log.Info("marshal", "stringer", v.String(), "raw", v) log.Info("hello", "val1", 1, "val2", map[string]int{"k": 1}) log.V(1).Info("you should see this") log.V(1).V(1).Info("you should NOT see this") diff --git a/example_test.go b/example_test.go index 04233e5..7e85bcf 100644 --- a/example_test.go +++ b/example_test.go @@ -21,6 +21,7 @@ import ( "time" "github.com/go-logr/zapr" + "github.com/go-logr/zapr/internal/types" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) @@ -52,6 +53,8 @@ func ExampleNewLogger() { log.Info("support for zap fields as key/value replacement is disabled", zap.Int("answer", 42)) log.Info("invalid key", 42, "answer") log.Info("missing value", "answer") + obj := types.ObjectRef{Name: "john", Namespace: "doe"} + log.Info("marshaler", "stringer", obj.String(), "raw", obj) // Output: // {"level":"info","ts":"TIMESTAMP","msg":"info message with default options"} // {"level":"error","ts":"TIMESTAMP","msg":"error message with default options","error":"some error"} @@ -61,6 +64,7 @@ func ExampleNewLogger() { // {"level":"info","ts":"TIMESTAMP","msg":"invalid key"} // {"level":"dpanic","ts":"TIMESTAMP","msg":"odd number of arguments passed as key-value pairs for logging","ignored key":"answer"} // {"level":"info","ts":"TIMESTAMP","msg":"missing value"} + // {"level":"info","ts":"TIMESTAMP","msg":"marshaler","stringer":"doe/john","raw":{"name":"john","namespace":"doe"}} } func ExampleLogInfoLevel() { diff --git a/go.mod b/go.mod index d73f9ac..71c4aea 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/go-logr/zapr go 1.16 require ( - github.com/go-logr/logr v1.1.0 + github.com/go-logr/logr v1.2.0 github.com/pkg/errors v0.9.1 // indirect github.com/stretchr/testify v1.7.0 go.uber.org/zap v1.19.0 diff --git a/go.sum b/go.sum index 6ac209c..98fe663 100644 --- a/go.sum +++ b/go.sum @@ -3,15 +3,10 @@ github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZx github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.0.0 h1:kH951GinvFVaQgy/ki/B3YYmQtRpExGigSJg6O8z5jo= -github.com/go-logr/logr v1.0.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.1.0-rc1 h1:XVIp2Gsi5tAUuqa8WmNJB6cVi4vGKPh5i/mMF/Hvk1Y= -github.com/go-logr/logr v1.1.0-rc1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.1.0 h1:nAbevmWlS2Ic4m4+/An5NXkaGqlqpbBgdcuThZxnZyI= -github.com/go-logr/logr v1.1.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -20,9 +15,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pohly/logr v1.0.1-0.20210817184539-cf703a64e7eb h1:7sco4gU4u7n+KjeYpWeDChU7+fxmCH75kXIsn2OjFeE= -github.com/pohly/logr v1.0.1-0.20210817184539-cf703a64e7eb/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -36,23 +28,17 @@ go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11 h1:Yq9t9jnGoR+dBuitxdo9l6Q7xh/zOyNnYUtDKaQ3x0E= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= diff --git a/internal/types/objectref.go b/internal/types/objectref.go new file mode 100644 index 0000000..34bb883 --- /dev/null +++ b/internal/types/objectref.go @@ -0,0 +1,50 @@ +/* +Copyright 2021 The logr Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package types + +import ( + "fmt" + + "github.com/go-logr/logr" +) + +// ObjectRef references a Kubernetes object +type ObjectRef struct { + Name string `json:"name"` + Namespace string `json:"namespace,omitempty"` +} + +func (ref ObjectRef) String() string { + if ref.Namespace != "" { + return fmt.Sprintf("%s/%s", ref.Namespace, ref.Name) + } + return ref.Name +} + +// MarshalLog ensures that loggers with structured output ignore the String method. +// +// We implement fmt.Stringer for non-structured logging, but we want the +// raw struct when using structured logs. Some logr implementations call +// String if it is present, so we want to convert this struct to something +// that doesn't have that method. +func (ref ObjectRef) MarshalLog() interface{} { + // Methods do not survive type definitions. + type forLog ObjectRef + return forLog(ref) +} + +var _ logr.Marshaler = ObjectRef{} diff --git a/zapr.go b/zapr.go index b87366c..5f99ca1 100644 --- a/zapr.go +++ b/zapr.go @@ -134,7 +134,7 @@ func (zl *zapLogger) handleFields(lvl int, args []interface{}, additional ...zap continue } if zl.panicMessages { - zl.l.WithOptions(zap.AddCallerSkip(1)).DPanic("strongly-typed Zap Field passed to logr", zap.Any("zap field", args[i])) + zl.l.WithOptions(zap.AddCallerSkip(1)).DPanic("strongly-typed Zap Field passed to logr", zapIt("zap field", args[i])) } break } @@ -142,7 +142,7 @@ func (zl *zapLogger) handleFields(lvl int, args []interface{}, additional ...zap // make sure this isn't a mismatched key if i == len(args)-1 { if zl.panicMessages { - zl.l.WithOptions(zap.AddCallerSkip(1)).DPanic("odd number of arguments passed as key-value pairs for logging", zap.Any("ignored key", args[i])) + zl.l.WithOptions(zap.AddCallerSkip(1)).DPanic("odd number of arguments passed as key-value pairs for logging", zapIt("ignored key", args[i])) } break } @@ -154,18 +154,27 @@ func (zl *zapLogger) handleFields(lvl int, args []interface{}, additional ...zap if !isString { // if the key isn't a string, DPanic and stop logging if zl.panicMessages { - zl.l.WithOptions(zap.AddCallerSkip(1)).DPanic("non-string key argument passed to logging, ignoring all later arguments", zap.Any("invalid key", key)) + zl.l.WithOptions(zap.AddCallerSkip(1)).DPanic("non-string key argument passed to logging, ignoring all later arguments", zapIt("invalid key", key)) } break } - fields = append(fields, zap.Any(keyStr, val)) + fields = append(fields, zapIt(keyStr, val)) i += 2 } return append(fields, additional...) } +func zapIt(field string, val interface{}) zap.Field { + // Handle types that implement logr.Marshaler: log the replacement + // object instead of the original one. + if marshaler, ok := val.(logr.Marshaler); ok { + val = marshaler.MarshalLog() + } + return zap.Any(field, val) +} + func (zl *zapLogger) Init(ri logr.RuntimeInfo) { zl.l = zl.l.WithOptions(zap.AddCallerSkip(ri.CallDepth)) }