diff --git a/vendor/github.com/go-logr/logr/.github/workflows/apidiff.yaml b/vendor/github.com/go-logr/logr/.github/workflows/apidiff.yaml index 340fffb0..a76a30c0 100644 --- a/vendor/github.com/go-logr/logr/.github/workflows/apidiff.yaml +++ b/vendor/github.com/go-logr/logr/.github/workflows/apidiff.yaml @@ -10,7 +10,7 @@ jobs: - name: Install Go uses: actions/setup-go@v2 with: - go-version: 1.16.x + go-version: 1.18.x - name: Add GOBIN to PATH run: echo "PATH=$(go env GOPATH)/bin:$PATH" >>$GITHUB_ENV - name: Install dependencies diff --git a/vendor/github.com/go-logr/logr/.github/workflows/assign.yaml b/vendor/github.com/go-logr/logr/.github/workflows/assign.yaml new file mode 100644 index 00000000..e1bfb970 --- /dev/null +++ b/vendor/github.com/go-logr/logr/.github/workflows/assign.yaml @@ -0,0 +1,21 @@ +name: Assign + +on: + issues: + types: [opened, reopened] + pull_request_target: + types: [opened, reopened] + +jobs: + assign: + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v6 + with: + script: | + github.rest.issues.addAssignees({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + assignees: ['thockin', 'pohly'] + }) diff --git a/vendor/github.com/go-logr/logr/.github/workflows/lint.yaml b/vendor/github.com/go-logr/logr/.github/workflows/lint.yaml index d76ae317..c46cb8b2 100644 --- a/vendor/github.com/go-logr/logr/.github/workflows/lint.yaml +++ b/vendor/github.com/go-logr/logr/.github/workflows/lint.yaml @@ -17,26 +17,5 @@ jobs: # Optional: show only new issues if it's a pull request. The default value is `false`. # only-new-issues: true - # golangci-lint command line arguments. - args: - -v - --max-same-issues 10 - --disable-all - --exclude-use-default=false - -E asciicheck - -E deadcode - -E errcheck - -E forcetypeassert - -E gocritic - -E gofmt - -E goimports - -E gosimple - -E govet - -E ineffassign - -E misspell - -E revive - -E staticcheck - -E structcheck - -E typecheck - -E unused - -E varcheck + # Read args from .golangci.yaml + # args: diff --git a/vendor/github.com/go-logr/logr/.github/workflows/tests.yaml b/vendor/github.com/go-logr/logr/.github/workflows/tests.yaml index e99538e8..2b7f2b59 100644 --- a/vendor/github.com/go-logr/logr/.github/workflows/tests.yaml +++ b/vendor/github.com/go-logr/logr/.github/workflows/tests.yaml @@ -6,14 +6,14 @@ jobs: test: strategy: matrix: - go-versions: [ 1.14.x, 1.15.x, 1.16.x ] + version: [ '1.15', '1.16', '1.17', '1.18' ] platform: [ ubuntu-latest, macos-latest, windows-latest ] runs-on: ${{ matrix.platform }} steps: - name: Install Go uses: actions/setup-go@v2 with: - go-version: ${{ matrix.go-version }} + go-version: ${{ matrix.version }} - name: Checkout code uses: actions/checkout@v2 - name: Build diff --git a/vendor/github.com/go-logr/logr/.golangci.yaml b/vendor/github.com/go-logr/logr/.golangci.yaml new file mode 100644 index 00000000..0cffafa7 --- /dev/null +++ b/vendor/github.com/go-logr/logr/.golangci.yaml @@ -0,0 +1,26 @@ +run: + timeout: 1m + tests: true + +linters: + disable-all: true + enable: + - asciicheck + - errcheck + - forcetypeassert + - gocritic + - gofmt + - goimports + - gosimple + - govet + - ineffassign + - misspell + - revive + - staticcheck + - typecheck + - unused + +issues: + exclude-use-default: false + max-issues-per-linter: 0 + max-same-issues: 10 diff --git a/vendor/github.com/go-logr/logr/README.md b/vendor/github.com/go-logr/logr/README.md index 69b0b40a..ab593118 100644 --- a/vendor/github.com/go-logr/logr/README.md +++ b/vendor/github.com/go-logr/logr/README.md @@ -1,5 +1,7 @@ # A minimal logging API for Go +[![Go Reference](https://pkg.go.dev/badge/github.com/go-logr/logr.svg)](https://pkg.go.dev/github.com/go-logr/logr) + logr offers an(other) opinion on how Go programs and libraries can do logging without becoming coupled to a particular logging implementation. This is not an implementation of logging - it is an API. In fact it is two APIs with two @@ -102,14 +104,19 @@ with higher verbosity means more (and less important) logs will be generated. There are implementations for the following logging libraries: -- **a function**: [funcr](https://github.com/go-logr/logr/tree/master/funcr) +- **a function** (can bridge to non-structured libraries): [funcr](https://github.com/go-logr/logr/tree/master/funcr) +- **a testing.T** (for use in Go tests, with JSON-like output): [testr](https://github.com/go-logr/logr/tree/master/testr) - **github.com/google/glog**: [glogr](https://github.com/go-logr/glogr) -- **k8s.io/klog**: [klogr](https://git.k8s.io/klog/klogr) +- **k8s.io/klog** (for Kubernetes): [klogr](https://git.k8s.io/klog/klogr) +- **a testing.T** (with klog-like text output): [ktesting](https://git.k8s.io/klog/ktesting) - **go.uber.org/zap**: [zapr](https://github.com/go-logr/zapr) - **log** (the Go standard library logger): [stdr](https://github.com/go-logr/stdr) - **github.com/sirupsen/logrus**: [logrusr](https://github.com/bombsimon/logrusr) - **github.com/wojas/genericr**: [genericr](https://github.com/wojas/genericr) (makes it easy to implement your own backend) - **logfmt** (Heroku style [logging](https://www.brandur.org/logfmt)): [logfmtr](https://github.com/iand/logfmtr) +- **github.com/rs/zerolog**: [zerologr](https://github.com/go-logr/zerologr) +- **github.com/go-kit/log**: [gokitlogr](https://github.com/tonglil/gokitlogr) (also compatible with github.com/go-kit/kit/log since v0.12.0) +- **bytes.Buffer** (writing to a buffer): [bufrlogr](https://github.com/tonglil/buflogr) (useful for ensuring values were logged, like during testing) ## FAQ diff --git a/vendor/github.com/go-logr/logr/_tools/apidiff.sh b/vendor/github.com/go-logr/logr/_tools/apidiff.sh old mode 100755 new mode 100644 diff --git a/vendor/github.com/go-logr/logr/benchmark/README.md b/vendor/github.com/go-logr/logr/benchmark/README.md index e0ecc511..f2687494 100644 --- a/vendor/github.com/go-logr/logr/benchmark/README.md +++ b/vendor/github.com/go-logr/logr/benchmark/README.md @@ -6,7 +6,7 @@ change. ## Running the benchmark ``` -$ go test -bench='.' ./benchmark/ +$ go test -bench='.' -test.benchmem ./benchmark/ ``` ## Fixing the benchmark diff --git a/vendor/github.com/go-logr/logr/benchmark/benchmark_test.go b/vendor/github.com/go-logr/logr/benchmark/benchmark_test.go index 3f746dff..cd111bcc 100644 --- a/vendor/github.com/go-logr/logr/benchmark/benchmark_test.go +++ b/vendor/github.com/go-logr/logr/benchmark/benchmark_test.go @@ -24,9 +24,6 @@ import ( "github.com/go-logr/logr/funcr" ) -func noop(prefix, args string) { -} - //go:noinline func doInfoOneArg(b *testing.B, log logr.Logger) { for i := 0; i < b.N; i++ { @@ -43,6 +40,16 @@ func doInfoSeveralArgs(b *testing.B, log logr.Logger) { } } +//go:noinline +func doInfoWithValues(b *testing.B, log logr.Logger) { + log = log.WithValues("k1", "str", "k2", 222, "k3", true, "k4", 1.0) + for i := 0; i < b.N; i++ { + log.Info("multi", + "bool", true, "string", "str", "int", 42, + "float", 3.14, "struct", struct{ X, Y int }{93, 76}) + } +} + //go:noinline func doV0Info(b *testing.B, log logr.Logger) { for i := 0; i < b.N; i++ { @@ -95,27 +102,71 @@ func doWithCallDepth(b *testing.B, log logr.Logger) { } } -func BenchmarkDiscardInfoOneArg(b *testing.B) { +type Tstringer struct{ s string } + +func (t Tstringer) String() string { + return t.s +} + +//go:noinline +func doStringerValue(b *testing.B, log logr.Logger) { + for i := 0; i < b.N; i++ { + log.Info("this is", "a", Tstringer{"stringer"}) + } +} + +type Terror struct{ s string } + +func (t Terror) Error() string { + return t.s +} + +//go:noinline +func doErrorValue(b *testing.B, log logr.Logger) { + for i := 0; i < b.N; i++ { + log.Info("this is", "an", Terror{"error"}) + } +} + +type Tmarshaler struct{ s string } + +func (t Tmarshaler) MarshalLog() interface{} { + return t.s +} + +//go:noinline +func doMarshalerValue(b *testing.B, log logr.Logger) { + for i := 0; i < b.N; i++ { + log.Info("this is", "a", Tmarshaler{"marshaler"}) + } +} + +func BenchmarkDiscardLogInfoOneArg(b *testing.B) { var log logr.Logger = logr.Discard() doInfoOneArg(b, log) } -func BenchmarkDiscardInfoSeveralArgs(b *testing.B) { +func BenchmarkDiscardLogInfoSeveralArgs(b *testing.B) { var log logr.Logger = logr.Discard() doInfoSeveralArgs(b, log) } -func BenchmarkDiscardV0Info(b *testing.B) { +func BenchmarkDiscardLogInfoWithValues(b *testing.B) { + var log logr.Logger = logr.Discard() + doInfoWithValues(b, log) +} + +func BenchmarkDiscardLogV0Info(b *testing.B) { var log logr.Logger = logr.Discard() doV0Info(b, log) } -func BenchmarkDiscardV9Info(b *testing.B) { +func BenchmarkDiscardLogV9Info(b *testing.B) { var log logr.Logger = logr.Discard() doV9Info(b, log) } -func BenchmarkDiscardError(b *testing.B) { +func BenchmarkDiscardLogError(b *testing.B) { var log logr.Logger = logr.Discard() doError(b, log) } @@ -130,42 +181,95 @@ func BenchmarkDiscardWithName(b *testing.B) { doWithName(b, log) } -func BenchmarkFuncrInfoOneArg(b *testing.B) { - var log logr.Logger = funcr.New(noop, funcr.Options{}) +func noopKV(prefix, args string) {} +func noopJSON(obj string) {} + +func BenchmarkFuncrLogInfoOneArg(b *testing.B) { + var log logr.Logger = funcr.New(noopKV, funcr.Options{}) + doInfoOneArg(b, log) +} + +func BenchmarkFuncrJSONLogInfoOneArg(b *testing.B) { + var log logr.Logger = funcr.NewJSON(noopJSON, funcr.Options{}) doInfoOneArg(b, log) } -func BenchmarkFuncrInfoSeveralArgs(b *testing.B) { - var log logr.Logger = funcr.New(noop, funcr.Options{}) +func BenchmarkFuncrLogInfoSeveralArgs(b *testing.B) { + var log logr.Logger = funcr.New(noopKV, funcr.Options{}) + doInfoSeveralArgs(b, log) +} + +func BenchmarkFuncrJSONLogInfoSeveralArgs(b *testing.B) { + var log logr.Logger = funcr.NewJSON(noopJSON, funcr.Options{}) doInfoSeveralArgs(b, log) } -func BenchmarkFuncrV0Info(b *testing.B) { - var log logr.Logger = funcr.New(noop, funcr.Options{}) +func BenchmarkFuncrLogInfoWithValues(b *testing.B) { + var log logr.Logger = funcr.New(noopKV, funcr.Options{}) + doInfoWithValues(b, log) +} + +func BenchmarkFuncrJSONLogInfoWithValues(b *testing.B) { + var log logr.Logger = funcr.NewJSON(noopJSON, funcr.Options{}) + doInfoWithValues(b, log) +} + +func BenchmarkFuncrLogV0Info(b *testing.B) { + var log logr.Logger = funcr.New(noopKV, funcr.Options{}) doV0Info(b, log) } -func BenchmarkFuncrV9Info(b *testing.B) { - var log logr.Logger = funcr.New(noop, funcr.Options{}) +func BenchmarkFuncrJSONLogV0Info(b *testing.B) { + var log logr.Logger = funcr.NewJSON(noopJSON, funcr.Options{}) + doV0Info(b, log) +} + +func BenchmarkFuncrLogV9Info(b *testing.B) { + var log logr.Logger = funcr.New(noopKV, funcr.Options{}) + doV9Info(b, log) +} + +func BenchmarkFuncrJSONLogV9Info(b *testing.B) { + var log logr.Logger = funcr.NewJSON(noopJSON, funcr.Options{}) doV9Info(b, log) } -func BenchmarkFuncrError(b *testing.B) { - var log logr.Logger = funcr.New(noop, funcr.Options{}) +func BenchmarkFuncrLogError(b *testing.B) { + var log logr.Logger = funcr.New(noopKV, funcr.Options{}) + doError(b, log) +} + +func BenchmarkFuncrJSONLogError(b *testing.B) { + var log logr.Logger = funcr.NewJSON(noopJSON, funcr.Options{}) doError(b, log) } func BenchmarkFuncrWithValues(b *testing.B) { - var log logr.Logger = funcr.New(noop, funcr.Options{}) + var log logr.Logger = funcr.New(noopKV, funcr.Options{}) doWithValues(b, log) } func BenchmarkFuncrWithName(b *testing.B) { - var log logr.Logger = funcr.New(noop, funcr.Options{}) + var log logr.Logger = funcr.New(noopKV, funcr.Options{}) doWithName(b, log) } func BenchmarkFuncrWithCallDepth(b *testing.B) { - var log logr.Logger = funcr.New(noop, funcr.Options{}) + var log logr.Logger = funcr.New(noopKV, funcr.Options{}) doWithCallDepth(b, log) } + +func BenchmarkFuncrJSONLogInfoStringerValue(b *testing.B) { + var log logr.Logger = funcr.NewJSON(noopJSON, funcr.Options{}) + doStringerValue(b, log) +} + +func BenchmarkFuncrJSONLogInfoErrorValue(b *testing.B) { + var log logr.Logger = funcr.NewJSON(noopJSON, funcr.Options{}) + doErrorValue(b, log) +} + +func BenchmarkFuncrJSONLogInfoMarshalerValue(b *testing.B) { + var log logr.Logger = funcr.NewJSON(noopJSON, funcr.Options{}) + doMarshalerValue(b, log) +} diff --git a/vendor/github.com/go-logr/logr/discard.go b/vendor/github.com/go-logr/logr/discard.go index 9d92a38f..99fe8be9 100644 --- a/vendor/github.com/go-logr/logr/discard.go +++ b/vendor/github.com/go-logr/logr/discard.go @@ -20,35 +20,5 @@ package logr // used whenever the caller is not interested in the logs. Logger instances // produced by this function always compare as equal. func Discard() Logger { - return Logger{ - level: 0, - sink: discardLogSink{}, - } -} - -// discardLogSink is a LogSink that discards all messages. -type discardLogSink struct{} - -// Verify that it actually implements the interface -var _ LogSink = discardLogSink{} - -func (l discardLogSink) Init(RuntimeInfo) { -} - -func (l discardLogSink) Enabled(int) bool { - return false -} - -func (l discardLogSink) Info(int, string, ...interface{}) { -} - -func (l discardLogSink) Error(error, string, ...interface{}) { -} - -func (l discardLogSink) WithValues(...interface{}) LogSink { - return l -} - -func (l discardLogSink) WithName(string) LogSink { - return l + return New(nil) } diff --git a/vendor/github.com/go-logr/logr/discard_test.go b/vendor/github.com/go-logr/logr/discard_test.go index c9a02de5..b46dcc01 100644 --- a/vendor/github.com/go-logr/logr/discard_test.go +++ b/vendor/github.com/go-logr/logr/discard_test.go @@ -24,7 +24,7 @@ import ( func TestDiscard(t *testing.T) { l := Discard() - if _, ok := l.GetSink().(discardLogSink); !ok { + if l.GetSink() != nil { t.Error("did not return the expected underlying type") } // Verify that none of the methods panic, there is not more we can test. @@ -32,18 +32,23 @@ func TestDiscard(t *testing.T) { l.Info("Hello world", "x", 1, "y", 2) l.V(1).Error(errors.New("foo"), "a", 123) if l.Enabled() { - t.Error("discardLogSink must always say it is disabled") + t.Error("discard loggers must always be disabled") } } func TestComparable(t *testing.T) { a := Discard() if !reflect.TypeOf(a).Comparable() { - t.Fatal("discardLogSink must be comparable") + t.Fatal("discard loggers must be comparable") } b := Discard() if a != b { - t.Fatal("any two discardLogSink must be equal") + t.Fatal("any two discard Loggers must be equal") + } + + c := Discard().V(2) + if b != c { + t.Fatal("any two discard Loggers must be equal") } } diff --git a/vendor/github.com/go-logr/logr/example_marshaler_secret_test.go b/vendor/github.com/go-logr/logr/example_marshaler_secret_test.go new file mode 100644 index 00000000..1e59af31 --- /dev/null +++ b/vendor/github.com/go-logr/logr/example_marshaler_secret_test.go @@ -0,0 +1,47 @@ +/* +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 logr_test + +import ( + "github.com/go-logr/logr" +) + +// ComplexObjectRef contains more fields than it wants to get logged. +type ComplexObjectRef struct { + Name string + Namespace string + Secret string +} + +func (ref ComplexObjectRef) MarshalLog() interface{} { + return struct { + Name, Namespace string + }{ + Name: ref.Name, + Namespace: ref.Namespace, + } +} + +var _ logr.Marshaler = ComplexObjectRef{} + +func ExampleMarshaler_secret() { + l := NewStdoutLogger() + secret := ComplexObjectRef{Namespace: "kube-system", Name: "some-secret", Secret: "do-not-log-me"} + l.Info("simplified", "secret", secret) + // Output: + // "level"=0 "msg"="simplified" "secret"={"Name":"some-secret","Namespace":"kube-system"} +} diff --git a/vendor/github.com/go-logr/logr/example_marshaler_test.go b/vendor/github.com/go-logr/logr/example_marshaler_test.go new file mode 100644 index 00000000..12fdaf3f --- /dev/null +++ b/vendor/github.com/go-logr/logr/example_marshaler_test.go @@ -0,0 +1,55 @@ +/* +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 logr_test + +import ( + "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 ref.Namespace + "/" + ref.Name + } + return ref.Name +} + +func (ref ObjectRef) MarshalLog() interface{} { + // 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. + type forLog ObjectRef // methods do not survive type definitions + return forLog(ref) +} + +var _ logr.Marshaler = ObjectRef{} + +func ExampleMarshaler() { + l := NewStdoutLogger() + pod := ObjectRef{Namespace: "kube-system", Name: "some-pod"} + l.Info("as string", "pod", pod.String()) + l.Info("as struct", "pod", pod) + // Output: + // "level"=0 "msg"="as string" "pod"="kube-system/some-pod" + // "level"=0 "msg"="as struct" "pod"={"name":"some-pod","namespace":"kube-system"} +} diff --git a/vendor/github.com/go-logr/logr/example_test.go b/vendor/github.com/go-logr/logr/example_test.go new file mode 100644 index 00000000..d18c96dc --- /dev/null +++ b/vendor/github.com/go-logr/logr/example_test.go @@ -0,0 +1,100 @@ +/* +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 logr_test + +import ( + "fmt" + + "github.com/go-logr/logr" + "github.com/go-logr/logr/funcr" +) + +// NewStdoutLogger returns a logr.Logger that prints to stdout. +func NewStdoutLogger() logr.Logger { + return funcr.New(func(prefix, args string) { + if prefix != "" { + fmt.Printf("%s: %s\n", prefix, args) + } else { + fmt.Println(args) + } + }, funcr.Options{}) +} + +func Example() { + l := NewStdoutLogger() + l.Info("default info log", "stringVal", "value", "intVal", 12345) + l.V(0).Info("V(0) info log", "stringVal", "value", "intVal", 12345) + l.Error(fmt.Errorf("an error"), "error log", "stringVal", "value", "intVal", 12345) + // Output: + // "level"=0 "msg"="default info log" "stringVal"="value" "intVal"=12345 + // "level"=0 "msg"="V(0) info log" "stringVal"="value" "intVal"=12345 + // "msg"="error log" "error"="an error" "stringVal"="value" "intVal"=12345 +} + +func ExampleLogger_Info() { + l := NewStdoutLogger() + l.Info("this is a V(0)-equivalent info log", "stringVal", "value", "intVal", 12345) + // Output: + // "level"=0 "msg"="this is a V(0)-equivalent info log" "stringVal"="value" "intVal"=12345 +} + +func ExampleLogger_Error() { + l := NewStdoutLogger() + l.Error(fmt.Errorf("the error"), "this is an error log", "stringVal", "value", "intVal", 12345) + l.Error(nil, "this is an error log with nil error", "stringVal", "value", "intVal", 12345) + // Output: + // "msg"="this is an error log" "error"="the error" "stringVal"="value" "intVal"=12345 + // "msg"="this is an error log with nil error" "error"=null "stringVal"="value" "intVal"=12345 +} + +func ExampleLogger_WithName() { + l := NewStdoutLogger() + l = l.WithName("name1") + l.Info("this is an info log", "stringVal", "value", "intVal", 12345) + l = l.WithName("name2") + l.Info("this is an info log", "stringVal", "value", "intVal", 12345) + // Output: + // name1: "level"=0 "msg"="this is an info log" "stringVal"="value" "intVal"=12345 + // name1/name2: "level"=0 "msg"="this is an info log" "stringVal"="value" "intVal"=12345 +} + +func ExampleLogger_WithValues() { + l := NewStdoutLogger() + l = l.WithValues("stringVal", "value", "intVal", 12345) + l = l.WithValues("boolVal", true) + l.Info("this is an info log", "floatVal", 3.1415) + // Output: + // "level"=0 "msg"="this is an info log" "stringVal"="value" "intVal"=12345 "boolVal"=true "floatVal"=3.1415 +} + +func ExampleLogger_V() { + l := NewStdoutLogger() + l.V(0).Info("V(0) info log") + l.V(1).Info("V(1) info log") + l.V(2).Info("V(2) info log") + // Output: + // "level"=0 "msg"="V(0) info log" +} + +func ExampleLogger_Enabled() { + l := NewStdoutLogger() + if loggerV := l.V(5); loggerV.Enabled() { + // Do something expensive. + loggerV.Info("this is an expensive log message") + } + // Output: +} diff --git a/vendor/github.com/go-logr/logr/examples/usage_example.go b/vendor/github.com/go-logr/logr/examples/usage_example.go index dc337f36..a826d9ca 100644 --- a/vendor/github.com/go-logr/logr/examples/usage_example.go +++ b/vendor/github.com/go-logr/logr/examples/usage_example.go @@ -35,31 +35,31 @@ import ( var objectMap = map[string]Object{ "obj1": Object{ - Name: "obj1", - Kind: "one", + Name: "obj1", + Kind: "one", Details: 33, }, "obj2": Object{ - Name: "obj2", - Kind: "two", + Name: "obj2", + Kind: "two", Details: "hi", }, "obj3": Object{ - Name: "obj3", - Kind: "one", + Name: "obj3", + Kind: "one", Details: 1, }, } type Object struct { - Name string - Kind string + Name string + Kind string Details interface{} } type Client struct { objects map[string]Object - log logr.Logger + log logr.Logger } func (c *Client) Get(key string) (Object, error) { @@ -82,7 +82,7 @@ func (c *Client) Save(obj Object) error { } func (c *Client) WatchNext() string { - time.Sleep(2*time.Second) + time.Sleep(2 * time.Second) keyInd := rand.Intn(len(c.objects)) @@ -99,9 +99,9 @@ func (c *Client) WatchNext() string { } type Controller struct { - log logr.Logger + log logr.Logger expectedKind string - client *Client + client *Client } func (c *Controller) Run() { @@ -144,13 +144,13 @@ func (c *Controller) Run() { func NewController(log logr.Logger, objectKind string) *Controller { ctrlLogger := log.WithName("controller").WithName(objectKind) client := &Client{ - log: ctrlLogger.WithName("client"), + log: ctrlLogger.WithName("client"), objects: objectMap, } return &Controller{ - log: ctrlLogger, + log: ctrlLogger, expectedKind: objectKind, - client: client, + client: client, } } diff --git a/vendor/github.com/go-logr/logr/funcr/example/main.go b/vendor/github.com/go-logr/logr/funcr/example/main.go index 303707b4..8f8bbd80 100644 --- a/vendor/github.com/go-logr/logr/funcr/example/main.go +++ b/vendor/github.com/go-logr/logr/funcr/example/main.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package main is an example of using funcr. package main import ( @@ -45,6 +46,7 @@ func main() { funcr.Options{ LogCaller: funcr.All, LogTimestamp: true, + Verbosity: 1, }) example(log.WithValues("module", "example")) } diff --git a/vendor/github.com/go-logr/logr/funcr/example_formatter_test.go b/vendor/github.com/go-logr/logr/funcr/example_formatter_test.go new file mode 100644 index 00000000..e5ed6ec2 --- /dev/null +++ b/vendor/github.com/go-logr/logr/funcr/example_formatter_test.go @@ -0,0 +1,103 @@ +/* +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 funcr_test + +import ( + "fmt" + "strings" + + "github.com/go-logr/logr" + "github.com/go-logr/logr/funcr" +) + +// NewStdoutLogger returns a logr.Logger that prints to stdout. +// It demonstrates how to implement a custom With* function which +// controls whether INFO or ERROR are printed in front of the log +// message. +func NewStdoutLogger() logr.Logger { + l := &stdoutlogger{ + Formatter: funcr.NewFormatter(funcr.Options{}), + } + return logr.New(l) +} + +type stdoutlogger struct { + funcr.Formatter + logMsgType bool +} + +func (l stdoutlogger) WithName(name string) logr.LogSink { + l.Formatter.AddName(name) + return &l +} + +func (l stdoutlogger) WithValues(kvList ...interface{}) logr.LogSink { + l.Formatter.AddValues(kvList) + return &l +} + +func (l stdoutlogger) WithCallDepth(depth int) logr.LogSink { + l.Formatter.AddCallDepth(depth) + return &l +} + +func (l stdoutlogger) Info(level int, msg string, kvList ...interface{}) { + prefix, args := l.FormatInfo(level, msg, kvList) + l.write("INFO", prefix, args) +} + +func (l stdoutlogger) Error(err error, msg string, kvList ...interface{}) { + prefix, args := l.FormatError(err, msg, kvList) + l.write("ERROR", prefix, args) +} + +func (l stdoutlogger) write(msgType, prefix, args string) { + var parts []string + if l.logMsgType { + parts = append(parts, msgType) + } + if prefix != "" { + parts = append(parts, prefix) + } + parts = append(parts, args) + fmt.Println(strings.Join(parts, ": ")) +} + +// WithLogMsgType returns a copy of the logger with new settings for +// logging the message type. It returns the original logger if the +// underlying LogSink is not a stdoutlogger. +func WithLogMsgType(log logr.Logger, logMsgType bool) logr.Logger { + if l, ok := log.GetSink().(*stdoutlogger); ok { + clone := *l + clone.logMsgType = logMsgType + log = log.WithSink(&clone) + } + return log +} + +// Assert conformance to the interfaces. +var _ logr.LogSink = &stdoutlogger{} +var _ logr.CallDepthLogSink = &stdoutlogger{} + +func ExampleFormatter() { + l := NewStdoutLogger() + l.Info("no message type") + WithLogMsgType(l, true).Info("with message type") + // Output: + // "level"=0 "msg"="no message type" + // INFO: "level"=0 "msg"="with message type" +} diff --git a/vendor/github.com/go-logr/logr/funcr/example_test.go b/vendor/github.com/go-logr/logr/funcr/example_test.go index 7aa2e2d6..53373a2d 100644 --- a/vendor/github.com/go-logr/logr/funcr/example_test.go +++ b/vendor/github.com/go-logr/logr/funcr/example_test.go @@ -18,12 +18,33 @@ package funcr_test import ( "fmt" - "strings" "github.com/go-logr/logr" "github.com/go-logr/logr/funcr" ) +func ExampleNew() { + var log logr.Logger = funcr.New(func(prefix, args string) { + fmt.Println(prefix, args) + }, funcr.Options{}) + + log = log.WithName("MyLogger") + log = log.WithValues("savedKey", "savedValue") + log.Info("the message", "key", "value") + // Output: MyLogger "level"=0 "msg"="the message" "savedKey"="savedValue" "key"="value" +} + +func ExampleNewJSON() { + var log logr.Logger = funcr.NewJSON(func(obj string) { + fmt.Println(obj) + }, funcr.Options{}) + + log = log.WithName("MyLogger") + log = log.WithValues("savedKey", "savedValue") + log.Info("the message", "key", "value") + // Output: {"logger":"MyLogger","level":0,"msg":"the message","savedKey":"savedValue","key":"value"} +} + func ExampleUnderlier() { var log logr.Logger = funcr.New(func(prefix, args string) { fmt.Println(prefix, args) @@ -36,80 +57,71 @@ func ExampleUnderlier() { // Output: hello world } -// NewStdoutLogger returns a logr.Logger that prints to stdout. -// It demonstrates how to implement a custom With* function which -// controls whether INFO or ERROR are printed in front of the log -// message. -func NewStdoutLogger() logr.Logger { - l := &stdoutlogger{ - Formatter: funcr.NewFormatter(funcr.Options{}), - } - return logr.New(l) -} - -type stdoutlogger struct { - funcr.Formatter - logMsgType bool -} - -func (l stdoutlogger) WithName(name string) logr.LogSink { - l.Formatter.AddName(name) - return &l -} - -func (l stdoutlogger) WithValues(kvList ...interface{}) logr.LogSink { - l.Formatter.AddValues(kvList) - return &l +func ExampleOptions() { + var log logr.Logger = funcr.NewJSON( + func(obj string) { fmt.Println(obj) }, + funcr.Options{ + LogCaller: funcr.All, + Verbosity: 1, // V(2) and higher is ignored. + }) + log.V(0).Info("V(0) message", "key", "value") + 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"} } -func (l stdoutlogger) WithCallDepth(depth int) logr.LogSink { - l.Formatter.AddCallDepth(depth) - return &l -} +func ExampleOptions_renderHooks() { + // prefix all builtin keys with "log:" + prefixSpecialKeys := func(kvList []interface{}) []interface{} { + for i := 0; i < len(kvList); i += 2 { + k, _ := kvList[i].(string) + kvList[i] = "log:" + k + } + return kvList + } -func (l stdoutlogger) Info(level int, msg string, kvList ...interface{}) { - prefix, args := l.FormatInfo(level, msg, kvList) - l.write("INFO", prefix, args) -} + // present saved values as a single JSON object + valuesAsObject := func(kvList []interface{}) []interface{} { + return []interface{}{"labels", funcr.PseudoStruct(kvList)} + } -func (l stdoutlogger) Error(err error, msg string, kvList ...interface{}) { - prefix, args := l.FormatError(err, msg, kvList) - l.write("ERROR", prefix, args) + var log logr.Logger = funcr.NewJSON( + func(obj string) { fmt.Println(obj) }, + funcr.Options{ + RenderBuiltinsHook: prefixSpecialKeys, + RenderValuesHook: valuesAsObject, + }) + log = log.WithName("MyLogger") + log = log.WithValues("savedKey1", "savedVal1") + log = log.WithValues("savedKey2", "savedVal2") + log.Info("the message", "key", "value") + // Output: {"log:logger":"MyLogger","log:level":0,"log:msg":"the message","labels":{"savedKey1":"savedVal1","savedKey2":"savedVal2"},"key":"value"} } -func (l stdoutlogger) write(msgType, prefix, args string) { - var parts []string - if l.logMsgType { - parts = append(parts, msgType) - } - if prefix != "" { - parts = append(parts, prefix) +func ExamplePseudoStruct() { + var log logr.Logger = funcr.NewJSON( + func(obj string) { fmt.Println(obj) }, + funcr.Options{}) + kv := []interface{}{ + "field1", 12345, + "field2", true, } - parts = append(parts, args) - fmt.Println(strings.Join(parts, ": ")) + log.Info("the message", "key", funcr.PseudoStruct(kv)) + // Output: {"logger":"","level":0,"msg":"the message","key":{"field1":12345,"field2":true}} } -// WithLogMsgType returns a copy of the logger with new settings for -// logging the message type. It returns the original logger if the -// underlying LogSink is not a stdoutlogger. -func WithLogMsgType(log logr.Logger, logMsgType bool) logr.Logger { - if l, ok := log.GetSink().(*stdoutlogger); ok { - clone := *l - clone.logMsgType = logMsgType - log = log.WithSink(&clone) +func ExampleOptions_maxLogDepth() { + type List struct { + Next *List } - return log -} - -// Assert conformance to the interfaces. -var _ logr.LogSink = &stdoutlogger{} -var _ logr.CallDepthLogSink = &stdoutlogger{} - -func ExampleFormatter() { - l := NewStdoutLogger() - l.Info("no message type") - WithLogMsgType(l, true).Info("with message type") - // Output: - // "level"=0 "msg"="no message type" - // INFO: "level"=0 "msg"="with message type" + l := List{} + l.Next = &l // recursive + + var log logr.Logger = funcr.NewJSON( + func(obj string) { fmt.Println(obj) }, + funcr.Options{MaxLogDepth: 4}) + log.Info("recursive", "list", l) + // Output: {"logger":"","level":0,"msg":"recursive","list":{"Next":{"Next":{"Next":{"Next":{"Next":""}}}}}} } diff --git a/vendor/github.com/go-logr/logr/funcr/funcr.go b/vendor/github.com/go-logr/logr/funcr/funcr.go index bb36d5ea..e52f0cd0 100644 --- a/vendor/github.com/go-logr/logr/funcr/funcr.go +++ b/vendor/github.com/go-logr/logr/funcr/funcr.go @@ -15,18 +15,29 @@ limitations under the License. */ // Package funcr implements formatting of structured log messages and -// optionally captures the call site. This will not call String or -// Error methods on values. +// optionally captures the call site and timestamp. // // The simplest way to use it is via its implementation of a // github.com/go-logr/logr.LogSink with output through an arbitrary -// "write" function. Alternatively, funcr can also be embedded inside -// a custom LogSink implementation. This is useful when the LogSink -// needs to implement additional methods. +// "write" function. See New and NewJSON for details. +// +// # Custom LogSinks +// +// For users who need more control, a funcr.Formatter can be embedded inside +// your own custom LogSink implementation. This is useful when the LogSink +// needs to implement additional methods, for example. +// +// # Formatting +// +// This will respect logr.Marshaler, fmt.Stringer, and error interfaces for +// values which are being logged. When rendering a struct, funcr will use Go's +// standard JSON tags (all except "string"). package funcr import ( "bytes" + "encoding" + "encoding/json" "fmt" "path/filepath" "reflect" @@ -40,7 +51,16 @@ import ( // New returns a logr.Logger which is implemented by an arbitrary function. func New(fn func(prefix, args string), opts Options) logr.Logger { - return logr.New(newSink(fn, opts)) + return logr.New(newSink(fn, NewFormatter(opts))) +} + +// NewJSON returns a logr.Logger which is implemented by an arbitrary function +// and produces JSON output. +func NewJSON(fn func(obj string), opts Options) logr.Logger { + fnWrapper := func(_, obj string) { + fn(obj) + } + return logr.New(newSink(fnWrapper, NewFormatterJSON(opts))) } // Underlier exposes access to the underlying logging function. Since @@ -51,9 +71,9 @@ type Underlier interface { GetUnderlying() func(prefix, args string) } -func newSink(fn func(prefix, args string), opts Options) logr.LogSink { +func newSink(fn func(prefix, args string), formatter Formatter) logr.LogSink { l := &fnlogger{ - Formatter: NewFormatter(opts), + Formatter: formatter, write: fn, } // For skipping fnlogger.Info and fnlogger.Error. @@ -67,13 +87,54 @@ type Options struct { // This has some overhead, so some users might not want it. LogCaller MessageClass + // LogCallerFunc tells funcr to also log the calling function name. This + // has no effect if caller logging is not enabled (see Options.LogCaller). + LogCallerFunc bool + // LogTimestamp tells funcr to add a "ts" key to log lines. This has some // overhead, so some users might not want it. LogTimestamp bool - // Verbosity tells funcr which V logs to be write. Higher values enable - // more logs. + // TimestampFormat tells funcr how to render timestamps when LogTimestamp + // is enabled. If not specified, a default format will be used. For more + // details, see docs for Go's time.Layout. + TimestampFormat string + + // Verbosity tells funcr which V logs to produce. Higher values enable + // more logs. Info logs at or below this level will be written, while logs + // above this level will be discarded. Verbosity int + + // RenderBuiltinsHook allows users to mutate the list of key-value pairs + // while a log line is being rendered. The kvList argument follows logr + // conventions - each pair of slice elements is comprised of a string key + // and an arbitrary value (verified and sanitized before calling this + // hook). The value returned must follow the same conventions. This hook + // can be used to audit or modify logged data. For example, you might want + // to prefix all of funcr's built-in keys with some string. This hook is + // only called for built-in (provided by funcr itself) key-value pairs. + // Equivalent hooks are offered for key-value pairs saved via + // logr.Logger.WithValues or Formatter.AddValues (see RenderValuesHook) and + // for user-provided pairs (see RenderArgsHook). + RenderBuiltinsHook func(kvList []interface{}) []interface{} + + // RenderValuesHook is the same as RenderBuiltinsHook, except that it is + // only called for key-value pairs saved via logr.Logger.WithValues. See + // RenderBuiltinsHook for more details. + RenderValuesHook func(kvList []interface{}) []interface{} + + // RenderArgsHook is the same as RenderBuiltinsHook, except that it is only + // called for key-value pairs passed directly to Info and Error. See + // RenderBuiltinsHook for more details. + RenderArgsHook func(kvList []interface{}) []interface{} + + // MaxLogDepth tells funcr how many levels of nested fields (e.g. a struct + // that contains a struct, etc.) it may log. Every time it finds a struct, + // slice, array, or map the depth is increased by one. When the maximum is + // reached, the value will be converted to a string indicating that the max + // depth has been exceeded. If this field is not specified, a default + // value will be used. + MaxLogDepth int } // MessageClass indicates which category or categories of messages to consider. @@ -90,8 +151,6 @@ const ( Error ) -const timestampFmt = "2006-01-02 15:04:05.000000" - // fnlogger inherits some of its LogSink implementation from Formatter // and just needs to add some glue code. type fnlogger struct { @@ -133,51 +192,183 @@ var _ logr.LogSink = &fnlogger{} var _ logr.CallDepthLogSink = &fnlogger{} var _ Underlier = &fnlogger{} -func flatten(kvList ...interface{}) string { - if len(kvList)%2 != 0 { - kvList = append(kvList, "") +// NewFormatter constructs a Formatter which emits a JSON-like key=value format. +func NewFormatter(opts Options) Formatter { + return newFormatter(opts, outputKeyValue) +} + +// NewFormatterJSON constructs a Formatter which emits strict JSON. +func NewFormatterJSON(opts Options) Formatter { + return newFormatter(opts, outputJSON) +} + +// Defaults for Options. +const defaultTimestampFormat = "2006-01-02 15:04:05.000000" +const defaultMaxLogDepth = 16 + +func newFormatter(opts Options, outfmt outputFormat) Formatter { + if opts.TimestampFormat == "" { + opts.TimestampFormat = defaultTimestampFormat + } + if opts.MaxLogDepth == 0 { + opts.MaxLogDepth = defaultMaxLogDepth } + f := Formatter{ + outputFormat: outfmt, + prefix: "", + values: nil, + depth: 0, + opts: &opts, + } + return f +} + +// Formatter is an opaque struct which can be embedded in a LogSink +// implementation. It should be constructed with NewFormatter. Some of +// its methods directly implement logr.LogSink. +type Formatter struct { + outputFormat outputFormat + prefix string + values []interface{} + valuesStr string + depth int + opts *Options +} + +// outputFormat indicates which outputFormat to use. +type outputFormat int + +const ( + // outputKeyValue emits a JSON-like key=value format, but not strict JSON. + outputKeyValue outputFormat = iota + // outputJSON emits strict JSON. + outputJSON +) + +// PseudoStruct is a list of key-value pairs that gets logged as a struct. +type PseudoStruct []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. buf := bytes.NewBuffer(make([]byte, 0, 1024)) + if f.outputFormat == outputJSON { + buf.WriteByte('{') + } + vals := builtins + if hook := f.opts.RenderBuiltinsHook; hook != nil { + vals = hook(f.sanitize(vals)) + } + f.flatten(buf, vals, false, false) // keys are ours, no need to escape + continuing := len(builtins) > 0 + if len(f.valuesStr) > 0 { + if continuing { + if f.outputFormat == outputJSON { + buf.WriteByte(',') + } else { + buf.WriteByte(' ') + } + } + continuing = true + buf.WriteString(f.valuesStr) + } + vals = args + if hook := f.opts.RenderArgsHook; hook != nil { + vals = hook(f.sanitize(vals)) + } + f.flatten(buf, vals, continuing, true) // escape user-provided keys + if f.outputFormat == outputJSON { + buf.WriteByte('}') + } + return buf.String() +} + +// flatten renders a list of key-value pairs into a buffer. If continuing is +// true, it assumes that the buffer has previous values and will emit a +// separator (which depends on the output format) before the first pair it +// writes. If escapeKeys is true, the keys are assumed to have +// non-JSON-compatible characters in them and must be evaluated for escapes. +// +// This function returns a potentially modified version of kvList, which +// ensures that there is a value for every key (adding a value if needed) and +// that each key is a string (substituting a key if needed). +func (f Formatter) flatten(buf *bytes.Buffer, kvList []interface{}, continuing bool, escapeKeys bool) []interface{} { + // This logic overlaps with sanitize() but saves one type-cast per key, + // which can be measurable. + if len(kvList)%2 != 0 { + kvList = append(kvList, noValue) + } for i := 0; i < len(kvList); i += 2 { k, ok := kvList[i].(string) if !ok { - k = fmt.Sprintf("", i/2) + k = f.nonStringKey(kvList[i]) + kvList[i] = k } v := kvList[i+1] - if i > 0 { - buf.WriteRune(' ') + if i > 0 || continuing { + if f.outputFormat == outputJSON { + buf.WriteByte(',') + } else { + // In theory the format could be something we don't understand. In + // practice, we control it, so it won't be. + buf.WriteByte(' ') + } + } + + if escapeKeys { + buf.WriteString(prettyString(k)) + } else { + // this is faster + buf.WriteByte('"') + buf.WriteString(k) + buf.WriteByte('"') } - buf.WriteRune('"') - buf.WriteString(k) - buf.WriteRune('"') - buf.WriteRune('=') - buf.WriteString(pretty(v)) + if f.outputFormat == outputJSON { + buf.WriteByte(':') + } else { + buf.WriteByte('=') + } + buf.WriteString(f.pretty(v)) } - return buf.String() + return kvList } -func pretty(value interface{}) string { - return prettyWithFlags(value, 0) +func (f Formatter) pretty(value interface{}) string { + return f.prettyWithFlags(value, 0, 0) } const ( - flagRawString = 0x1 + flagRawStruct = 0x1 // do not print braces on structs ) // TODO: This is not fast. Most of the overhead goes here. -func prettyWithFlags(value interface{}, flags uint32) string { +func (f Formatter) prettyWithFlags(value interface{}, flags uint32, depth int) string { + if depth > f.opts.MaxLogDepth { + return `""` + } + + // Handle types that take full control of logging. + if v, ok := value.(logr.Marshaler); ok { + // Replace the value with what the type wants to get logged. + // That then gets handled below via reflection. + value = invokeMarshaler(v) + } + + // Handle types that want to format themselves. + switch v := value.(type) { + case fmt.Stringer: + value = invokeStringer(v) + case error: + value = invokeError(v) + } + // Handling the most common types without reflect is a small perf win. switch v := value.(type) { case bool: return strconv.FormatBool(v) case string: - if flags&flagRawString > 0 { - return v - } - // This is empirically faster than strings.Builder. - return `"` + v + `"` + return prettyString(v) case int: return strconv.FormatInt(int64(v), 10) case int8: @@ -204,6 +395,30 @@ func prettyWithFlags(value interface{}, flags uint32) string { return strconv.FormatFloat(float64(v), 'f', -1, 32) case float64: return strconv.FormatFloat(v, 'f', -1, 64) + case complex64: + return `"` + strconv.FormatComplex(complex128(v), 'f', -1, 64) + `"` + case complex128: + return `"` + strconv.FormatComplex(v, 'f', -1, 128) + `"` + case PseudoStruct: + buf := bytes.NewBuffer(make([]byte, 0, 1024)) + v = f.sanitize(v) + if flags&flagRawStruct == 0 { + buf.WriteByte('{') + } + for i := 0; i < len(v); i += 2 { + if i > 0 { + buf.WriteByte(',') + } + k, _ := v[i].(string) // sanitize() above means no need to check success + // arbitrary keys might need escaping + buf.WriteString(prettyString(k)) + buf.WriteByte(':') + buf.WriteString(f.prettyWithFlags(v[i+1], 0, depth+1)) + } + if flags&flagRawStruct == 0 { + buf.WriteByte('}') + } + return buf.String() } buf := bytes.NewBuffer(make([]byte, 0, 256)) @@ -216,11 +431,7 @@ func prettyWithFlags(value interface{}, flags uint32) string { case reflect.Bool: return strconv.FormatBool(v.Bool()) case reflect.String: - if flags&flagRawString > 0 { - return v.String() - } - // This is empirically faster than strings.Builder. - return `"` + v.String() + `"` + return prettyString(v.String()) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return strconv.FormatInt(int64(v.Int()), 10) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: @@ -229,106 +440,270 @@ func prettyWithFlags(value interface{}, flags uint32) string { return strconv.FormatFloat(float64(v.Float()), 'f', -1, 32) case reflect.Float64: return strconv.FormatFloat(v.Float(), 'f', -1, 64) + case reflect.Complex64: + return `"` + strconv.FormatComplex(complex128(v.Complex()), 'f', -1, 64) + `"` + case reflect.Complex128: + return `"` + strconv.FormatComplex(v.Complex(), 'f', -1, 128) + `"` case reflect.Struct: - buf.WriteRune('{') + if flags&flagRawStruct == 0 { + buf.WriteByte('{') + } + printComma := false // testing i>0 is not enough because of JSON omitted fields for i := 0; i < t.NumField(); i++ { - f := t.Field(i) - if f.PkgPath != "" { + fld := t.Field(i) + if fld.PkgPath != "" { // reflect says this field is only defined for non-exported fields. continue } - if i > 0 { - buf.WriteRune(',') + if !v.Field(i).CanInterface() { + // reflect isn't clear exactly what this means, but we can't use it. + continue } - buf.WriteRune('"') - name := f.Name - if tag, found := f.Tag.Lookup("json"); found { + name := "" + omitempty := false + if tag, found := fld.Tag.Lookup("json"); found { + if tag == "-" { + continue + } if comma := strings.Index(tag, ","); comma != -1 { - name = tag[:comma] + if n := tag[:comma]; n != "" { + name = n + } + rest := tag[comma:] + if strings.Contains(rest, ",omitempty,") || strings.HasSuffix(rest, ",omitempty") { + omitempty = true + } } else { name = tag } } + if omitempty && isEmpty(v.Field(i)) { + continue + } + if printComma { + buf.WriteByte(',') + } + printComma = true // if we got here, we are rendering a field + if fld.Anonymous && fld.Type.Kind() == reflect.Struct && name == "" { + buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), flags|flagRawStruct, depth+1)) + continue + } + if name == "" { + name = fld.Name + } + // field names can't contain characters which need escaping + buf.WriteByte('"') buf.WriteString(name) - buf.WriteRune('"') - buf.WriteRune(':') - buf.WriteString(pretty(v.Field(i).Interface())) + buf.WriteByte('"') + buf.WriteByte(':') + buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), 0, depth+1)) + } + if flags&flagRawStruct == 0 { + buf.WriteByte('}') } - buf.WriteRune('}') return buf.String() case reflect.Slice, reflect.Array: - buf.WriteRune('[') + // If this is outputing as JSON make sure this isn't really a json.RawMessage. + // If so just emit "as-is" and don't pretty it as that will just print + // it as [X,Y,Z,...] which isn't terribly useful vs the string form you really want. + if f.outputFormat == outputJSON { + if rm, ok := value.(json.RawMessage); ok { + // If it's empty make sure we emit an empty value as the array style would below. + if len(rm) > 0 { + buf.Write(rm) + } else { + buf.WriteString("null") + } + return buf.String() + } + } + buf.WriteByte('[') for i := 0; i < v.Len(); i++ { if i > 0 { - buf.WriteRune(',') + buf.WriteByte(',') } e := v.Index(i) - buf.WriteString(pretty(e.Interface())) + buf.WriteString(f.prettyWithFlags(e.Interface(), 0, depth+1)) } - buf.WriteRune(']') + buf.WriteByte(']') return buf.String() case reflect.Map: - buf.WriteRune('{') + buf.WriteByte('{') // This does not sort the map keys, for best perf. it := v.MapRange() i := 0 for it.Next() { if i > 0 { - buf.WriteRune(',') + buf.WriteByte(',') } - // JSON only does string keys. - buf.WriteRune('"') - buf.WriteString(prettyWithFlags(it.Key().Interface(), flagRawString)) - buf.WriteRune('"') - buf.WriteRune(':') - buf.WriteString(pretty(it.Value().Interface())) + // If a map key supports TextMarshaler, use it. + keystr := "" + if m, ok := it.Key().Interface().(encoding.TextMarshaler); ok { + txt, err := m.MarshalText() + if err != nil { + keystr = fmt.Sprintf("", err.Error()) + } else { + keystr = string(txt) + } + keystr = prettyString(keystr) + } else { + // prettyWithFlags will produce already-escaped values + keystr = f.prettyWithFlags(it.Key().Interface(), 0, depth+1) + if t.Key().Kind() != reflect.String { + // JSON only does string keys. Unlike Go's standard JSON, we'll + // convert just about anything to a string. + keystr = prettyString(keystr) + } + } + buf.WriteString(keystr) + buf.WriteByte(':') + buf.WriteString(f.prettyWithFlags(it.Value().Interface(), 0, depth+1)) i++ } - buf.WriteRune('}') + buf.WriteByte('}') return buf.String() case reflect.Ptr, reflect.Interface: - return pretty(v.Elem().Interface()) + if v.IsNil() { + return "null" + } + return f.prettyWithFlags(v.Elem().Interface(), 0, depth) } return fmt.Sprintf(`""`, t.Kind().String()) } -type callerID struct { - File string `json:"file"` - Line int `json:"line"` +func prettyString(s string) string { + // Avoid escaping (which does allocations) if we can. + if needsEscape(s) { + return strconv.Quote(s) + } + b := bytes.NewBuffer(make([]byte, 0, 1024)) + b.WriteByte('"') + b.WriteString(s) + b.WriteByte('"') + return b.String() } -// NewFormatter constructs a Formatter. -func NewFormatter(opts Options) Formatter { - f := Formatter{ - prefix: "", - values: nil, - depth: 0, - logCaller: opts.LogCaller, - logTimestamp: opts.LogTimestamp, - verbosity: opts.Verbosity, +// needsEscape determines whether the input string needs to be escaped or not, +// without doing any allocations. +func needsEscape(s string) bool { + for _, r := range s { + if !strconv.IsPrint(r) || r == '\\' || r == '"' { + return true + } } - return f + return false } -// Formatter is an opaque struct which can be embedded in a LogSink -// implementation. It should be constructed with NewFormatter. Some of -// its methods directly implement logr.LogSink. -type Formatter struct { - prefix string - values []interface{} - depth int - logCaller MessageClass - logTimestamp bool - verbosity int +func isEmpty(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Complex64, reflect.Complex128: + return v.Complex() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } + return false +} + +func invokeMarshaler(m logr.Marshaler) (ret interface{}) { + defer func() { + if r := recover(); r != nil { + ret = fmt.Sprintf("", r) + } + }() + return m.MarshalLog() +} + +func invokeStringer(s fmt.Stringer) (ret string) { + defer func() { + if r := recover(); r != nil { + ret = fmt.Sprintf("", r) + } + }() + return s.String() +} + +func invokeError(e error) (ret string) { + defer func() { + if r := recover(); r != nil { + ret = fmt.Sprintf("", r) + } + }() + return e.Error() } -func (f Formatter) caller() callerID { +// Caller represents the original call site for a log line, after considering +// logr.Logger.WithCallDepth and logr.Logger.WithCallStackHelper. The File and +// Line fields will always be provided, while the Func field is optional. +// Users can set the render hook fields in Options to examine logged key-value +// pairs, one of which will be {"caller", Caller} if the Options.LogCaller +// field is enabled for the given MessageClass. +type Caller struct { + // File is the basename of the file for this call site. + File string `json:"file"` + // Line is the line number in the file for this call site. + Line int `json:"line"` + // Func is the function name for this call site, or empty if + // Options.LogCallerFunc is not enabled. + Func string `json:"function,omitempty"` +} + +func (f Formatter) caller() Caller { // +1 for this frame, +1 for Info/Error. - _, file, line, ok := runtime.Caller(f.depth + 2) + pc, file, line, ok := runtime.Caller(f.depth + 2) if !ok { - return callerID{"", 0} + return Caller{"", 0, ""} + } + fn := "" + if f.opts.LogCallerFunc { + if fp := runtime.FuncForPC(pc); fp != nil { + fn = fp.Name() + } } - return callerID{filepath.Base(file), line} + + return Caller{filepath.Base(file), line, fn} +} + +const noValue = "" + +func (f Formatter) nonStringKey(v interface{}) string { + return fmt.Sprintf("", f.snippet(v)) +} + +// snippet produces a short snippet string of an arbitrary value. +func (f Formatter) snippet(v interface{}) string { + const snipLen = 16 + + snip := f.pretty(v) + if len(snip) > snipLen { + snip = snip[:snipLen] + } + return snip +} + +// sanitize ensures that a list of key-value pairs has a value for every key +// (adding a value if needed) and that each key is a string (substituting a key +// if needed). +func (f Formatter) sanitize(kvList []interface{}) []interface{} { + if len(kvList)%2 != 0 { + kvList = append(kvList, noValue) + } + for i := 0; i < len(kvList); i += 2 { + _, ok := kvList[i].(string) + if !ok { + kvList[i] = f.nonStringKey(kvList[i]) + } + } + return kvList } // Init configures this Formatter from runtime info, such as the call depth @@ -340,7 +715,7 @@ func (f *Formatter) Init(info logr.RuntimeInfo) { // Enabled checks whether an info message at the given level should be logged. func (f Formatter) Enabled(level int) bool { - return level <= f.verbosity + return level <= f.opts.Verbosity } // GetDepth returns the current depth of this Formatter. This is useful for @@ -349,30 +724,40 @@ func (f Formatter) GetDepth() int { return f.depth } -// FormatInfo flattens an Info log message into strings. -// The prefix will be empty when no names were set. +// FormatInfo renders an Info log message into strings. The prefix will be +// empty when no names were set (via AddNames), or when the output is +// configured for JSON. func (f Formatter) FormatInfo(level int, msg string, kvList []interface{}) (prefix, argsStr string) { args := make([]interface{}, 0, 64) // using a constant here impacts perf - if f.logTimestamp { - args = append(args, "ts", time.Now().Format(timestampFmt)) + prefix = f.prefix + if f.outputFormat == outputJSON { + args = append(args, "logger", prefix) + prefix = "" } - if f.logCaller == All || f.logCaller == Info { + if f.opts.LogTimestamp { + args = append(args, "ts", time.Now().Format(f.opts.TimestampFormat)) + } + if policy := f.opts.LogCaller; policy == All || policy == Info { args = append(args, "caller", f.caller()) } args = append(args, "level", level, "msg", msg) - args = append(args, f.values...) - args = append(args, kvList...) - return f.prefix, flatten(args...) + return prefix, f.render(args, kvList) } -// FormatError flattens an Error log message into strings. -// The prefix will be empty when no names were set. +// FormatError renders an Error log message into strings. The prefix will be +// empty when no names were set (via AddNames), or when the output is +// configured for JSON. func (f Formatter) FormatError(err error, msg string, kvList []interface{}) (prefix, argsStr string) { args := make([]interface{}, 0, 64) // using a constant here impacts perf - if f.logTimestamp { - args = append(args, "ts", time.Now().Format(timestampFmt)) + prefix = f.prefix + if f.outputFormat == outputJSON { + args = append(args, "logger", prefix) + prefix = "" + } + if f.opts.LogTimestamp { + args = append(args, "ts", time.Now().Format(f.opts.TimestampFormat)) } - if f.logCaller == All || f.logCaller == Error { + if policy := f.opts.LogCaller; policy == All || policy == Error { args = append(args, "caller", f.caller()) } args = append(args, "msg", msg) @@ -381,9 +766,7 @@ func (f Formatter) FormatError(err error, msg string, kvList []interface{}) (pre loggableErr = err.Error() } args = append(args, "error", loggableErr) - args = append(args, f.values...) - args = append(args, kvList...) - return f.prefix, flatten(args...) + return f.prefix, f.render(args, kvList) } // AddName appends the specified name. funcr uses '/' characters to separate @@ -402,6 +785,16 @@ func (f *Formatter) AddValues(kvList []interface{}) { // Three slice args forces a copy. n := len(f.values) f.values = append(f.values[:n:n], kvList...) + + vals := f.values + if hook := f.opts.RenderValuesHook; hook != nil { + vals = hook(f.sanitize(vals)) + } + + // Pre-render values, so we don't have to do it on each Info/Error call. + buf := bytes.NewBuffer(make([]byte, 0, 1024)) + f.flatten(buf, vals, false, true) // escape user-provided keys + f.valuesStr = buf.String() } // AddCallDepth increases the number of stack-frames to skip when attributing diff --git a/vendor/github.com/go-logr/logr/funcr/funcr_test.go b/vendor/github.com/go-logr/logr/funcr/funcr_test.go index 70d0d31e..8af39705 100644 --- a/vendor/github.com/go-logr/logr/funcr/funcr_test.go +++ b/vendor/github.com/go-logr/logr/funcr/funcr_test.go @@ -20,12 +20,14 @@ import ( "encoding/json" "fmt" "path/filepath" + "reflect" "runtime" "testing" "github.com/go-logr/logr" ) +// Will be handled via reflection instead of type assertions. type substr string func ptrint(i int) *int { @@ -35,54 +37,353 @@ func ptrstr(s string) *string { return &s } +// point implements encoding.TextMarshaler and can be used as a map key. +type point struct{ x, y int } + +func (p point) MarshalText() ([]byte, error) { + return []byte(fmt.Sprintf("(%d, %d)", p.x, p.y)), nil +} + +// pointErr implements encoding.TextMarshaler but returns an error. +type pointErr struct{ x, y int } + +func (p pointErr) MarshalText() ([]byte, error) { + return nil, fmt.Errorf("uh oh: %d, %d", p.x, p.y) +} + +// Logging this should result in the MarshalLog() value. +type Tmarshaler struct{ val string } + +func (t Tmarshaler) MarshalLog() interface{} { + return struct{ Inner string }{"I am a logr.Marshaler"} +} + +func (t Tmarshaler) String() string { + return "String(): you should not see this" +} + +func (t Tmarshaler) Error() string { + return "Error(): you should not see this" +} + +// Logging this should result in a panic. +type Tmarshalerpanic struct{ val string } + +func (t Tmarshalerpanic) MarshalLog() interface{} { + panic("Tmarshalerpanic") +} + +// Logging this should result in the String() value. +type Tstringer struct{ val string } + +func (t Tstringer) String() string { + return "I am a fmt.Stringer" +} + +func (t Tstringer) Error() string { + return "Error(): you should not see this" +} + +// Logging this should result in a panic. +type Tstringerpanic struct{ val string } + +func (t Tstringerpanic) String() string { + panic("Tstringerpanic") +} + +// Logging this should result in the Error() value. +type Terror struct{ val string } + +func (t Terror) Error() string { + return "I am an error" +} + +// Logging this should result in a panic. +type Terrorpanic struct{ val string } + +func (t Terrorpanic) Error() string { + panic("Terrorpanic") +} + +type TjsontagsString struct { + String0 string `json:"-"` // first field ignored + String1 string `json:"string1"` // renamed + String2 string `json:"-"` // ignored + String3 string `json:"-,"` // named "-" + String4 string `json:"string4,omitempty"` // renamed, ignore if empty + String5 string `json:","` // no-op + String6 string `json:",omitempty"` // ignore if empty +} + +type TjsontagsBool struct { + Bool0 bool `json:"-"` // first field ignored + Bool1 bool `json:"bool1"` // renamed + Bool2 bool `json:"-"` // ignored + Bool3 bool `json:"-,"` // named "-" + Bool4 bool `json:"bool4,omitempty"` // renamed, ignore if empty + Bool5 bool `json:","` // no-op + Bool6 bool `json:",omitempty"` // ignore if empty +} + +type TjsontagsInt struct { + Int0 int `json:"-"` // first field ignored + Int1 int `json:"int1"` // renamed + Int2 int `json:"-"` // ignored + Int3 int `json:"-,"` // named "-" + Int4 int `json:"int4,omitempty"` // renamed, ignore if empty + Int5 int `json:","` // no-op + Int6 int `json:",omitempty"` // ignore if empty +} + +type TjsontagsUint struct { + Uint0 int `json:"-"` // first field ignored + Uint1 uint `json:"uint1"` // renamed + Uint2 uint `json:"-"` // ignored + Uint3 uint `json:"-,"` // named "-" + Uint4 uint `json:"uint4,omitempty"` // renamed, ignore if empty + Uint5 uint `json:","` // no-op + Uint6 uint `json:",omitempty"` // ignore if empty +} + +type TjsontagsFloat struct { + Float0 float64 `json:"-"` // first field ignored + Float1 float64 `json:"float1"` // renamed + Float2 float64 `json:"-"` // ignored + Float3 float64 `json:"-,"` // named "-" + Float4 float64 `json:"float4,omitempty"` // renamed, ignore if empty + Float5 float64 `json:","` // no-op + Float6 float64 `json:",omitempty"` // ignore if empty +} + +type TjsontagsComplex struct { + Complex0 complex128 `json:"-"` // first field ignored + Complex1 complex128 `json:"complex1"` // renamed + Complex2 complex128 `json:"-"` // ignored + Complex3 complex128 `json:"-,"` // named "-" + Complex4 complex128 `json:"complex4,omitempty"` // renamed, ignore if empty + Complex5 complex128 `json:","` // no-op + Complex6 complex128 `json:",omitempty"` // ignore if empty +} + +type TjsontagsPtr struct { + Ptr0 *string `json:"-"` // first field ignored + Ptr1 *string `json:"ptr1"` // renamed + Ptr2 *string `json:"-"` // ignored + Ptr3 *string `json:"-,"` // named "-" + Ptr4 *string `json:"ptr4,omitempty"` // renamed, ignore if empty + Ptr5 *string `json:","` // no-op + Ptr6 *string `json:",omitempty"` // ignore if empty +} + +type TjsontagsArray struct { + Array0 [2]string `json:"-"` // first field ignored + Array1 [2]string `json:"array1"` // renamed + Array2 [2]string `json:"-"` // ignored + Array3 [2]string `json:"-,"` // named "-" + Array4 [2]string `json:"array4,omitempty"` // renamed, ignore if empty + Array5 [2]string `json:","` // no-op + Array6 [2]string `json:",omitempty"` // ignore if empty +} + +type TjsontagsSlice struct { + Slice0 []string `json:"-"` // first field ignored + Slice1 []string `json:"slice1"` // renamed + Slice2 []string `json:"-"` // ignored + Slice3 []string `json:"-,"` // named "-" + Slice4 []string `json:"slice4,omitempty"` // renamed, ignore if empty + Slice5 []string `json:","` // no-op + Slice6 []string `json:",omitempty"` // ignore if empty +} + +type TjsontagsMap struct { + Map0 map[string]string `json:"-"` // first field ignored + Map1 map[string]string `json:"map1"` // renamed + Map2 map[string]string `json:"-"` // ignored + Map3 map[string]string `json:"-,"` // named "-" + Map4 map[string]string `json:"map4,omitempty"` // renamed, ignore if empty + Map5 map[string]string `json:","` // no-op + Map6 map[string]string `json:",omitempty"` // ignore if empty +} + +type Tinnerstruct struct { + Inner string +} +type Tinnerint int +type Tinnermap map[string]string +type Tinnerslice []string + +type Tembedstruct struct { + Tinnerstruct + Outer string +} + +type Tembednonstruct struct { + Tinnerint + Tinnermap + Tinnerslice +} + +type Tinner1 Tinnerstruct +type Tinner2 Tinnerstruct +type Tinner3 Tinnerstruct +type Tinner4 Tinnerstruct +type Tinner5 Tinnerstruct +type Tinner6 Tinnerstruct + +type Tembedjsontags struct { + Outer string + Tinner1 `json:"inner1"` + Tinner2 `json:"-"` + Tinner3 `json:"-,"` + Tinner4 `json:"inner4,omitempty"` + Tinner5 `json:","` + Tinner6 `json:"inner6,omitempty"` +} + +type Trawjson struct { + Message json.RawMessage `json:"message"` +} + func TestPretty(t *testing.T) { - cases := []interface{}{ - "strval", - substr("substrval"), - true, - false, - int(93), - int8(93), - int16(93), - int32(93), - int64(93), - int(-93), - int8(-93), - int16(-93), - int32(-93), - int64(-93), - uint(93), - uint8(93), - uint16(93), - uint32(93), - uint64(93), - uintptr(93), - float32(93.76), - float64(93.76), - ptrint(93), - ptrstr("pstrval"), - []int{9, 3, 7, 6}, - [4]int{9, 3, 7, 6}, - struct { - Int int - String string + // used below + newStr := func(s string) *string { + return &s + } + + cases := []struct { + val interface{} + exp string // used in cases where JSON can't handle it + }{{ + val: "strval", + }, { + val: "strval\nwith\t\"escapes\"", + }, { + val: substr("substrval"), + }, { + val: substr("substrval\nwith\t\"escapes\""), + }, { + val: true, + }, { + val: false, + }, { + val: int(93), + }, { + val: int8(93), + }, { + val: int16(93), + }, { + val: int32(93), + }, { + val: int64(93), + }, { + val: int(-93), + }, { + val: int8(-93), + }, { + val: int16(-93), + }, { + val: int32(-93), + }, { + val: int64(-93), + }, { + val: uint(93), + }, { + val: uint8(93), + }, { + val: uint16(93), + }, { + val: uint32(93), + }, { + val: uint64(93), + }, { + val: uintptr(93), + }, { + val: float32(93.76), + }, { + val: float64(93.76), + }, { + val: complex64(93i), + exp: `"(0+93i)"`, + }, { + val: complex128(93i), + exp: `"(0+93i)"`, + }, { + val: ptrint(93), + }, { + val: ptrstr("pstrval"), + }, { + val: []int{}, + }, { + val: []int(nil), + exp: `[]`, + }, { + val: []int{9, 3, 7, 6}, + }, { + val: []string{"str", "with\tescape"}, + }, { + val: []substr{"substr", "with\tescape"}, + }, { + val: [4]int{9, 3, 7, 6}, + }, { + val: [2]string{"str", "with\tescape"}, + }, { + val: [2]substr{"substr", "with\tescape"}, + }, { + val: struct { + Int int + notExported string + String string }{ - 93, "seventy-six", + 93, "you should not see this", "seventy-six", }, - map[string]int{ + }, { + val: map[string]int{}, + }, { + val: map[string]int(nil), + exp: `{}`, + }, { + val: map[string]int{ "nine": 3, }, - map[substr]int{ + }, { + val: map[string]int{ + "with\tescape": 76, + }, + }, { + val: map[substr]int{ "nine": 3, }, - fmt.Errorf("error"), - struct { + }, { + val: map[substr]int{ + "with\tescape": 76, + }, + }, { + val: map[int]int{ + 9: 3, + }, + }, { + val: map[float64]int{ + 9.5: 3, + }, + exp: `{"9.5":3}`, + }, { + val: map[point]int{ + {x: 1, y: 2}: 3, + }, + }, { + val: map[pointErr]int{ + {x: 1, y: 2}: 3, + }, + exp: `{"":3}`, + }, { + val: struct { X int `json:"x"` Y int `json:"y"` }{ 93, 76, }, - struct { + }, { + val: struct { X []int Y map[int]int Z struct{ P, Q int } @@ -91,20 +392,288 @@ func TestPretty(t *testing.T) { map[int]int{9: 3}, struct{ P, Q int }{9, 3}, }, - []struct{ X, Y string }{ + }, { + val: []struct{ X, Y string }{ {"nine", "three"}, {"seven", "six"}, + {"with\t", "\tescapes"}, }, - } + }, { + val: struct { + A *int + B *int + C interface{} + D interface{} + }{ + B: ptrint(1), + D: interface{}(2), + }, + }, { + val: Tmarshaler{"foobar"}, + exp: `{"Inner":"I am a logr.Marshaler"}`, + }, { + val: &Tmarshaler{"foobar"}, + exp: `{"Inner":"I am a logr.Marshaler"}`, + }, { + val: (*Tmarshaler)(nil), + exp: `""`, + }, { + val: Tmarshalerpanic{"foobar"}, + exp: `""`, + }, { + val: Tstringer{"foobar"}, + exp: `"I am a fmt.Stringer"`, + }, { + val: &Tstringer{"foobar"}, + exp: `"I am a fmt.Stringer"`, + }, { + val: (*Tstringer)(nil), + exp: `""`, + }, { + val: Tstringerpanic{"foobar"}, + exp: `""`, + }, { + val: Terror{"foobar"}, + exp: `"I am an error"`, + }, { + val: &Terror{"foobar"}, + exp: `"I am an error"`, + }, { + val: (*Terror)(nil), + exp: `""`, + }, { + val: Terrorpanic{"foobar"}, + exp: `""`, + }, { + val: TjsontagsString{ + String1: "v1", + String2: "v2", + String3: "v3", + String4: "v4", + String5: "v5", + String6: "v6", + }, + }, { + val: TjsontagsString{}, + }, { + val: TjsontagsBool{ + Bool1: true, + Bool2: true, + Bool3: true, + Bool4: true, + Bool5: true, + Bool6: true, + }, + }, { + val: TjsontagsBool{}, + }, { + val: TjsontagsInt{ + Int1: 1, + Int2: 2, + Int3: 3, + Int4: 4, + Int5: 5, + Int6: 6, + }, + }, { + val: TjsontagsInt{}, + }, { + val: TjsontagsUint{ + Uint1: 1, + Uint2: 2, + Uint3: 3, + Uint4: 4, + Uint5: 5, + Uint6: 6, + }, + }, { + val: TjsontagsUint{}, + }, { + val: TjsontagsFloat{ + Float1: 1.1, + Float2: 2.2, + Float3: 3.3, + Float4: 4.4, + Float5: 5.5, + Float6: 6.6, + }, + }, { + val: TjsontagsFloat{}, + }, { + val: TjsontagsComplex{ + Complex1: 1i, + Complex2: 2i, + Complex3: 3i, + Complex4: 4i, + Complex5: 5i, + Complex6: 6i, + }, + exp: `{"complex1":"(0+1i)","-":"(0+3i)","complex4":"(0+4i)","Complex5":"(0+5i)","Complex6":"(0+6i)"}`, + }, { + val: TjsontagsComplex{}, + exp: `{"complex1":"(0+0i)","-":"(0+0i)","Complex5":"(0+0i)"}`, + }, { + val: TjsontagsPtr{ + Ptr1: newStr("1"), + Ptr2: newStr("2"), + Ptr3: newStr("3"), + Ptr4: newStr("4"), + Ptr5: newStr("5"), + Ptr6: newStr("6"), + }, + }, { + val: TjsontagsPtr{}, + }, { + val: TjsontagsArray{ + Array1: [2]string{"v1", "v1"}, + Array2: [2]string{"v2", "v2"}, + Array3: [2]string{"v3", "v3"}, + Array4: [2]string{"v4", "v4"}, + Array5: [2]string{"v5", "v5"}, + Array6: [2]string{"v6", "v6"}, + }, + }, { + val: TjsontagsArray{}, + }, { + val: TjsontagsSlice{ + Slice1: []string{"v1", "v1"}, + Slice2: []string{"v2", "v2"}, + Slice3: []string{"v3", "v3"}, + Slice4: []string{"v4", "v4"}, + Slice5: []string{"v5", "v5"}, + Slice6: []string{"v6", "v6"}, + }, + }, { + val: TjsontagsSlice{}, + exp: `{"slice1":[],"-":[],"Slice5":[]}`, + }, { + val: TjsontagsMap{ + Map1: map[string]string{"k1": "v1"}, + Map2: map[string]string{"k2": "v2"}, + Map3: map[string]string{"k3": "v3"}, + Map4: map[string]string{"k4": "v4"}, + Map5: map[string]string{"k5": "v5"}, + Map6: map[string]string{"k6": "v6"}, + }, + }, { + val: TjsontagsMap{}, + exp: `{"map1":{},"-":{},"Map5":{}}`, + }, { + val: Tembedstruct{}, + }, { + val: Tembednonstruct{}, + exp: `{"Tinnerint":0,"Tinnermap":{},"Tinnerslice":[]}`, + }, { + val: Tembedjsontags{}, + }, { + val: PseudoStruct(makeKV("f1", 1, "f2", true, "f3", []int{})), + exp: `{"f1":1,"f2":true,"f3":[]}`, + }, { + val: map[TjsontagsString]int{ + {String1: `"quoted"`, String4: `unquoted`}: 1, + }, + exp: `{"{\"string1\":\"\\\"quoted\\\"\",\"-\":\"\",\"string4\":\"unquoted\",\"String5\":\"\"}":1}`, + }, { + val: map[TjsontagsInt]int{ + {Int1: 1, Int2: 2}: 3, + }, + exp: `{"{\"int1\":1,\"-\":0,\"Int5\":0}":3}`, + }, { + val: map[[2]struct{ S string }]int{ + {{S: `"quoted"`}, {S: "unquoted"}}: 1, + }, + exp: `{"[{\"S\":\"\\\"quoted\\\"\"},{\"S\":\"unquoted\"}]":1}`, + }, { + val: TjsontagsComplex{}, + exp: `{"complex1":"(0+0i)","-":"(0+0i)","Complex5":"(0+0i)"}`, + }, { + val: TjsontagsPtr{ + Ptr1: newStr("1"), + Ptr2: newStr("2"), + Ptr3: newStr("3"), + Ptr4: newStr("4"), + Ptr5: newStr("5"), + Ptr6: newStr("6"), + }, + }, { + val: TjsontagsPtr{}, + }, { + val: TjsontagsArray{ + Array1: [2]string{"v1", "v1"}, + Array2: [2]string{"v2", "v2"}, + Array3: [2]string{"v3", "v3"}, + Array4: [2]string{"v4", "v4"}, + Array5: [2]string{"v5", "v5"}, + Array6: [2]string{"v6", "v6"}, + }, + }, { + val: TjsontagsArray{}, + }, { + val: TjsontagsSlice{ + Slice1: []string{"v1", "v1"}, + Slice2: []string{"v2", "v2"}, + Slice3: []string{"v3", "v3"}, + Slice4: []string{"v4", "v4"}, + Slice5: []string{"v5", "v5"}, + Slice6: []string{"v6", "v6"}, + }, + }, { + val: TjsontagsSlice{}, + exp: `{"slice1":[],"-":[],"Slice5":[]}`, + }, { + val: TjsontagsMap{ + Map1: map[string]string{"k1": "v1"}, + Map2: map[string]string{"k2": "v2"}, + Map3: map[string]string{"k3": "v3"}, + Map4: map[string]string{"k4": "v4"}, + Map5: map[string]string{"k5": "v5"}, + Map6: map[string]string{"k6": "v6"}, + }, + }, { + val: TjsontagsMap{}, + exp: `{"map1":{},"-":{},"Map5":{}}`, + }, { + val: Tembedstruct{}, + }, { + val: Tembednonstruct{}, + exp: `{"Tinnerint":0,"Tinnermap":{},"Tinnerslice":[]}`, + }, { + val: Tembedjsontags{}, + }, { + val: PseudoStruct(makeKV("f1", 1, "f2", true, "f3", []int{})), + exp: `{"f1":1,"f2":true,"f3":[]}`, + }, { + val: map[TjsontagsString]int{ + {String1: `"quoted"`, String4: `unquoted`}: 1, + }, + exp: `{"{\"string1\":\"\\\"quoted\\\"\",\"-\":\"\",\"string4\":\"unquoted\",\"String5\":\"\"}":1}`, + }, { + val: map[TjsontagsInt]int{ + {Int1: 1, Int2: 2}: 3, + }, + exp: `{"{\"int1\":1,\"-\":0,\"Int5\":0}":3}`, + }, { + val: map[[2]struct{ S string }]int{ + {{S: `"quoted"`}, {S: "unquoted"}}: 1, + }, + exp: `{"[{\"S\":\"\\\"quoted\\\"\"},{\"S\":\"unquoted\"}]":1}`, + }} + f := NewFormatter(Options{}) for i, tc := range cases { - ours := pretty(tc) - std, err := json.Marshal(tc) - if err != nil { - t.Errorf("[%d]: unexpected error: %v", i, err) + ours := f.pretty(tc.val) + want := "" + if tc.exp != "" { + want = tc.exp + } else { + jb, err := json.Marshal(tc.val) + if err != nil { + t.Fatalf("[%d]: unexpected error: %v\ngot: %q", i, err, ours) + } + want = string(jb) } - if ours != string(std) { - t.Errorf("[%d]: expected %q, got %q", i, std, ours) + if ours != want { + t.Errorf("[%d]:\n\texpected %q\n\tgot %q", i, want, ours) } } } @@ -113,38 +682,157 @@ func makeKV(args ...interface{}) []interface{} { return args } -func TestFlatten(t *testing.T) { +func TestRender(t *testing.T) { + // used below + raw := &Trawjson{} + marshal := &TjsontagsInt{} + var err error + raw.Message, err = json.Marshal(marshal) + if err != nil { + t.Fatalf("json.Marshal error: %v", err) + } + + testCases := []struct { + name string + builtins []interface{} + values []interface{} + args []interface{} + expectKV string + expectJSON string + }{{ + name: "nil", + expectKV: "", + expectJSON: "{}", + }, { + name: "empty", + builtins: []interface{}{}, + values: []interface{}{}, + args: []interface{}{}, + expectKV: "", + expectJSON: "{}", + }, { + name: "primitives", + builtins: makeKV("int1", 1, "int2", 2), + values: makeKV("str1", "ABC", "str2", "DEF"), + args: makeKV("bool1", true, "bool2", false), + expectKV: `"int1"=1 "int2"=2 "str1"="ABC" "str2"="DEF" "bool1"=true "bool2"=false`, + expectJSON: `{"int1":1,"int2":2,"str1":"ABC","str2":"DEF","bool1":true,"bool2":false}`, + }, { + name: "pseudo structs", + builtins: makeKV("int", PseudoStruct(makeKV("intsub", 1))), + values: makeKV("str", PseudoStruct(makeKV("strsub", "2"))), + args: makeKV("bool", PseudoStruct(makeKV("boolsub", true))), + expectKV: `"int"={"intsub":1} "str"={"strsub":"2"} "bool"={"boolsub":true}`, + expectJSON: `{"int":{"intsub":1},"str":{"strsub":"2"},"bool":{"boolsub":true}}`, + }, { + name: "escapes", + builtins: makeKV("\"1\"", 1), // will not be escaped, but should never happen + values: makeKV("\tstr", "ABC"), // escaped + args: makeKV("bool\n", true), // escaped + expectKV: `""1""=1 "\tstr"="ABC" "bool\n"=true`, + expectJSON: `{""1"":1,"\tstr":"ABC","bool\n":true}`, + }, { + name: "missing value", + builtins: makeKV("builtin"), + values: makeKV("value"), + args: makeKV("arg"), + expectKV: `"builtin"="" "value"="" "arg"=""`, + expectJSON: `{"builtin":"","value":"","arg":""}`, + }, { + name: "non-string key int", + builtins: makeKV(123, "val"), // should never happen + values: makeKV(456, "val"), + args: makeKV(789, "val"), + expectKV: `""="val" ""="val" ""="val"`, + expectJSON: `{"":"val","":"val","":"val"}`, + }, { + name: "non-string key struct", + builtins: makeKV(struct { // will not be escaped, but should never happen + F1 string + F2 int + }{"builtin", 123}, "val"), + values: makeKV(struct { + F1 string + F2 int + }{"value", 456}, "val"), + args: makeKV(struct { + F1 string + F2 int + }{"arg", 789}, "val"), + expectKV: `""="val" ""="val" ""="val"`, + expectJSON: `{"":"val","":"val","":"val"}`, + }, { + name: "json rendering with json.RawMessage", + args: makeKV("key", raw), + expectKV: `"key"={"message":[123,34,105,110,116,49,34,58,48,44,34,45,34,58,48,44,34,73,110,116,53,34,58,48,125]}`, + expectJSON: `{"key":{"message":{"int1":0,"-":0,"Int5":0}}}`, + }, { + name: "byte array not json.RawMessage", + args: makeKV("key", []byte{1, 2, 3, 4}), + expectKV: `"key"=[1,2,3,4]`, + expectJSON: `{"key":[1,2,3,4]}`, + }, { + name: "json rendering with empty json.RawMessage", + args: makeKV("key", &Trawjson{}), + expectKV: `"key"={"message":[]}`, + expectJSON: `{"key":{"message":null}}`, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + test := func(t *testing.T, formatter Formatter, expect string) { + formatter.AddValues(tc.values) + r := formatter.render(tc.builtins, tc.args) + if r != expect { + t.Errorf("wrong output:\nexpected %v\n got %v", expect, r) + } + } + t.Run("KV", func(t *testing.T) { + test(t, NewFormatter(Options{}), tc.expectKV) + }) + t.Run("JSON", func(t *testing.T) { + test(t, NewFormatterJSON(Options{}), tc.expectJSON) + }) + }) + } +} + +func TestSanitize(t *testing.T) { testCases := []struct { name string kv []interface{} - expect string + expect []interface{} }{{ - name: "nil", - kv: nil, - expect: "", - }, { name: "empty", kv: []interface{}{}, - expect: "", + expect: []interface{}{}, }, { - name: "primitives", + name: "already sane", kv: makeKV("int", 1, "str", "ABC", "bool", true), - expect: `"int"=1 "str"="ABC" "bool"=true`, + expect: makeKV("int", 1, "str", "ABC", "bool", true), }, { name: "missing value", kv: makeKV("key"), - expect: `"key"=""`, + expect: makeKV("key", ""), }, { - name: "non-string key", + name: "non-string key int", kv: makeKV(123, "val"), - expect: `""="val"`, + expect: makeKV("", "val"), + }, { + name: "non-string key struct", + kv: makeKV(struct { + F1 string + F2 int + }{"f1", 8675309}, "val"), + expect: makeKV(``, "val"), }} + f := NewFormatterJSON(Options{}) for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - r := flatten(tc.kv...) - if r != tc.expect { - t.Errorf("expected %q, got %q", tc.expect, r) + r := f.sanitize(tc.kv) + if !reflect.DeepEqual(r, tc.expect) { + t.Errorf("wrong output:\nexpected %q\n got %q", tc.expect, r) } }) } @@ -152,7 +840,7 @@ func TestFlatten(t *testing.T) { func TestEnabled(t *testing.T) { t.Run("default V", func(t *testing.T) { - log := newSink(func(prefix, args string) {}, Options{}) + log := newSink(func(prefix, args string) {}, NewFormatter(Options{})) if !log.Enabled(0) { t.Errorf("expected true") } @@ -161,7 +849,7 @@ func TestEnabled(t *testing.T) { } }) t.Run("V=9", func(t *testing.T) { - log := newSink(func(prefix, args string) {}, Options{Verbosity: 9}) + log := newSink(func(prefix, args string) {}, NewFormatter(Options{Verbosity: 9})) if !log.Enabled(8) { t.Errorf("expected true") } @@ -200,7 +888,7 @@ func TestInfo(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { cap := &capture{} - sink := newSink(cap.Func, Options{}) + sink := newSink(cap.Func, NewFormatter(Options{})) sink.Info(0, "msg", tc.args...) if cap.log != tc.expect { t.Errorf("\nexpected %q\n got %q", tc.expect, cap.log) @@ -212,41 +900,80 @@ func TestInfo(t *testing.T) { func TestInfoWithCaller(t *testing.T) { t.Run("LogCaller=All", func(t *testing.T) { cap := &capture{} - sink := newSink(cap.Func, Options{LogCaller: All}) + sink := newSink(cap.Func, NewFormatter(Options{LogCaller: All})) sink.Info(0, "msg") _, file, line, _ := runtime.Caller(0) expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d} "level"=0 "msg"="msg"`, filepath.Base(file), line-1) if cap.log != expect { t.Errorf("\nexpected %q\n got %q", expect, cap.log) } + sink.Error(fmt.Errorf("error"), "msg") + _, file, line, _ = runtime.Caller(0) + expect = fmt.Sprintf(` "caller"={"file":%q,"line":%d} "msg"="msg" "error"="error"`, filepath.Base(file), line-1) + if cap.log != expect { + t.Errorf("\nexpected %q\n got %q", expect, cap.log) + } + }) + t.Run("LogCaller=All, LogCallerFunc=true", func(t *testing.T) { + thisFunc := "github.com/go-logr/logr/funcr.TestInfoWithCaller.func2" + cap := &capture{} + sink := newSink(cap.Func, NewFormatter(Options{LogCaller: All, LogCallerFunc: true})) + sink.Info(0, "msg") + _, file, line, _ := runtime.Caller(0) + expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d,"function":%q} "level"=0 "msg"="msg"`, filepath.Base(file), line-1, thisFunc) + if cap.log != expect { + t.Errorf("\nexpected %q\n got %q", expect, cap.log) + } + sink.Error(fmt.Errorf("error"), "msg") + _, file, line, _ = runtime.Caller(0) + expect = fmt.Sprintf(` "caller"={"file":%q,"line":%d,"function":%q} "msg"="msg" "error"="error"`, filepath.Base(file), line-1, thisFunc) + if cap.log != expect { + t.Errorf("\nexpected %q\n got %q", expect, cap.log) + } }) t.Run("LogCaller=Info", func(t *testing.T) { cap := &capture{} - sink := newSink(cap.Func, Options{LogCaller: Info}) + sink := newSink(cap.Func, NewFormatter(Options{LogCaller: Info})) sink.Info(0, "msg") _, file, line, _ := runtime.Caller(0) expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d} "level"=0 "msg"="msg"`, filepath.Base(file), line-1) if cap.log != expect { t.Errorf("\nexpected %q\n got %q", expect, cap.log) } + sink.Error(fmt.Errorf("error"), "msg") + expect = ` "msg"="msg" "error"="error"` + if cap.log != expect { + t.Errorf("\nexpected %q\n got %q", expect, cap.log) + } }) t.Run("LogCaller=Error", func(t *testing.T) { cap := &capture{} - sink := newSink(cap.Func, Options{LogCaller: Error}) + sink := newSink(cap.Func, NewFormatter(Options{LogCaller: Error})) sink.Info(0, "msg") expect := ` "level"=0 "msg"="msg"` if cap.log != expect { t.Errorf("\nexpected %q\n got %q", expect, cap.log) } + sink.Error(fmt.Errorf("error"), "msg") + _, file, line, _ := runtime.Caller(0) + expect = fmt.Sprintf(` "caller"={"file":%q,"line":%d} "msg"="msg" "error"="error"`, filepath.Base(file), line-1) + if cap.log != expect { + t.Errorf("\nexpected %q\n got %q", expect, cap.log) + } }) t.Run("LogCaller=None", func(t *testing.T) { cap := &capture{} - sink := newSink(cap.Func, Options{LogCaller: None}) + sink := newSink(cap.Func, NewFormatter(Options{LogCaller: None})) sink.Info(0, "msg") expect := ` "level"=0 "msg"="msg"` if cap.log != expect { t.Errorf("\nexpected %q\n got %q", expect, cap.log) } + sink.Error(fmt.Errorf("error"), "msg") + expect = ` "msg"="msg" "error"="error"` + if cap.log != expect { + t.Errorf("\nexpected %q\n got %q", expect, cap.log) + } }) } @@ -268,7 +995,7 @@ func TestError(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { cap := &capture{} - sink := newSink(cap.Func, Options{}) + sink := newSink(cap.Func, NewFormatter(Options{})) sink.Error(fmt.Errorf("err"), "msg", tc.args...) if cap.log != tc.expect { t.Errorf("\nexpected %q\n got %q", tc.expect, cap.log) @@ -280,7 +1007,7 @@ func TestError(t *testing.T) { func TestErrorWithCaller(t *testing.T) { t.Run("LogCaller=All", func(t *testing.T) { cap := &capture{} - sink := newSink(cap.Func, Options{LogCaller: All}) + sink := newSink(cap.Func, NewFormatter(Options{LogCaller: All})) sink.Error(fmt.Errorf("err"), "msg") _, file, line, _ := runtime.Caller(0) expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d} "msg"="msg" "error"="err"`, filepath.Base(file), line-1) @@ -290,7 +1017,7 @@ func TestErrorWithCaller(t *testing.T) { }) t.Run("LogCaller=Error", func(t *testing.T) { cap := &capture{} - sink := newSink(cap.Func, Options{LogCaller: Error}) + sink := newSink(cap.Func, NewFormatter(Options{LogCaller: Error})) sink.Error(fmt.Errorf("err"), "msg") _, file, line, _ := runtime.Caller(0) expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d} "msg"="msg" "error"="err"`, filepath.Base(file), line-1) @@ -300,7 +1027,7 @@ func TestErrorWithCaller(t *testing.T) { }) t.Run("LogCaller=Info", func(t *testing.T) { cap := &capture{} - sink := newSink(cap.Func, Options{LogCaller: Info}) + sink := newSink(cap.Func, NewFormatter(Options{LogCaller: Info})) sink.Error(fmt.Errorf("err"), "msg") expect := ` "msg"="msg" "error"="err"` if cap.log != expect { @@ -309,7 +1036,7 @@ func TestErrorWithCaller(t *testing.T) { }) t.Run("LogCaller=None", func(t *testing.T) { cap := &capture{} - sink := newSink(cap.Func, Options{LogCaller: None}) + sink := newSink(cap.Func, NewFormatter(Options{LogCaller: None})) sink.Error(fmt.Errorf("err"), "msg") expect := ` "msg"="msg" "error"="err"` if cap.log != expect { @@ -339,7 +1066,7 @@ func TestInfoWithName(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { cap := &capture{} - sink := newSink(cap.Func, Options{}) + sink := newSink(cap.Func, NewFormatter(Options{})) for _, n := range tc.names { sink = sink.WithName(n) } @@ -372,7 +1099,7 @@ func TestErrorWithName(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { cap := &capture{} - sink := newSink(cap.Func, Options{}) + sink := newSink(cap.Func, NewFormatter(Options{})) for _, n := range tc.names { sink = sink.WithName(n) } @@ -405,12 +1132,17 @@ func TestInfoWithValues(t *testing.T) { values: makeKV("one", 1, "two", 2), args: makeKV("k", "v"), expect: ` "level"=0 "msg"="msg" "one"=1 "two"=2 "k"="v"`, + }, { + name: "dangling", + values: makeKV("dangling"), + args: makeKV("k", "v"), + expect: ` "level"=0 "msg"="msg" "dangling"="" "k"="v"`, }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { cap := &capture{} - sink := newSink(cap.Func, Options{}) + sink := newSink(cap.Func, NewFormatter(Options{})) sink = sink.WithValues(tc.values...) sink.Info(0, "msg", tc.args...) if cap.log != tc.expect { @@ -441,12 +1173,17 @@ func TestErrorWithValues(t *testing.T) { values: makeKV("one", 1, "two", 2), args: makeKV("k", "v"), expect: ` "msg"="msg" "error"="err" "one"=1 "two"=2 "k"="v"`, + }, { + name: "dangling", + values: makeKV("dangling"), + args: makeKV("k", "v"), + expect: ` "msg"="msg" "error"="err" "dangling"="" "k"="v"`, }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { cap := &capture{} - sink := newSink(cap.Func, Options{}) + sink := newSink(cap.Func, NewFormatter(Options{})) sink = sink.WithValues(tc.values...) sink.Error(fmt.Errorf("err"), "msg", tc.args...) if cap.log != tc.expect { @@ -459,7 +1196,7 @@ func TestErrorWithValues(t *testing.T) { func TestInfoWithCallDepth(t *testing.T) { t.Run("one", func(t *testing.T) { cap := &capture{} - sink := newSink(cap.Func, Options{LogCaller: All}) + sink := newSink(cap.Func, NewFormatter(Options{LogCaller: All})) dSink, _ := sink.(logr.CallDepthLogSink) sink = dSink.WithCallDepth(1) sink.Info(0, "msg") @@ -474,7 +1211,7 @@ func TestInfoWithCallDepth(t *testing.T) { func TestErrorWithCallDepth(t *testing.T) { t.Run("one", func(t *testing.T) { cap := &capture{} - sink := newSink(cap.Func, Options{LogCaller: All}) + sink := newSink(cap.Func, NewFormatter(Options{LogCaller: All})) dSink, _ := sink.(logr.CallDepthLogSink) sink = dSink.WithCallDepth(1) sink.Error(fmt.Errorf("err"), "msg") @@ -485,3 +1222,17 @@ func TestErrorWithCallDepth(t *testing.T) { } }) } + +func TestOptionsTimestampFormat(t *testing.T) { + cap := &capture{} + // This timestamp format contains none of the characters that are + // considered placeholders, so will produce a constant result. + sink := newSink(cap.Func, NewFormatter(Options{LogTimestamp: true, TimestampFormat: "TIMESTAMP"})) + dSink, _ := sink.(logr.CallDepthLogSink) + sink = dSink.WithCallDepth(1) + sink.Info(0, "msg") + expect := ` "ts"="TIMESTAMP" "level"=0 "msg"="msg"` + if cap.log != expect { + t.Errorf("\nexpected %q\n got %q", expect, cap.log) + } +} diff --git a/vendor/github.com/go-logr/logr/logr.go b/vendor/github.com/go-logr/logr/logr.go index c8c4555f..e027aea3 100644 --- a/vendor/github.com/go-logr/logr/logr.go +++ b/vendor/github.com/go-logr/logr/logr.go @@ -30,20 +30,26 @@ limitations under the License. // "structured logging". // // With Go's standard log package, we might write: -// log.Printf("setting target value %s", targetValue) +// +// log.Printf("setting target value %s", targetValue) // // With logr's structured logging, we'd write: -// logger.Info("setting target", "value", targetValue) +// +// logger.Info("setting target", "value", targetValue) // // Errors are much the same. Instead of: -// log.Printf("failed to open the pod bay door for user %s: %v", user, err) +// +// log.Printf("failed to open the pod bay door for user %s: %v", user, err) // // We'd write: -// logger.Error(err, "failed to open the pod bay door", "user", user) +// +// logger.Error(err, "failed to open the pod bay door", "user", user) // // Info() and Error() are very similar, but they are separate methods so that // LogSink implementations can choose to do things like attach additional -// information (such as stack traces) on calls to Error(). +// information (such as stack traces) on calls to Error(). Error() messages are +// always logged, regardless of the current verbosity. If there is no error +// instance available, passing nil is valid. // // # Verbosity // @@ -53,14 +59,17 @@ limitations under the License. // Log-lines with V-levels that are not enabled (as per the LogSink) will not // be written. Level V(0) is the default, and logger.V(0).Info() has the same // meaning as logger.Info(). Negative V-levels have the same meaning as V(0). +// Error messages do not have a verbosity level and are always logged. // // Where we might have written: -// if flVerbose >= 2 { -// log.Printf("an unusual thing happened") -// } +// +// if flVerbose >= 2 { +// log.Printf("an unusual thing happened") +// } // // We can write: -// logger.V(2).Info("an unusual thing happened") +// +// logger.V(2).Info("an unusual thing happened") // // # Logger Names // @@ -68,7 +77,7 @@ limitations under the License. // that instance have additional context. For example, you might want to add // a subsystem name: // -// logger.WithName("compactor").Info("started", "time", time.Now()) +// logger.WithName("compactor").Info("started", "time", time.Now()) // // The WithName() method returns a new Logger, which can be passed to // constructors or other functions for further use. Repeated use of WithName() @@ -86,16 +95,18 @@ limitations under the License. // you might want to create a Logger instance per managed object: // // With the standard log package, we might write: -// log.Printf("decided to set field foo to value %q for object %s/%s", -// targetValue, object.Namespace, object.Name) +// +// log.Printf("decided to set field foo to value %q for object %s/%s", +// targetValue, object.Namespace, object.Name) // // With logr we'd write: -// // Elsewhere: set up the logger to log the object name. -// obj.logger = mainLogger.WithValues( -// "name", obj.name, "namespace", obj.namespace) // -// // later on... -// obj.logger.Info("setting foo", "value", targetValue) +// // Elsewhere: set up the logger to log the object name. +// obj.logger = mainLogger.WithValues( +// "name", obj.name, "namespace", obj.namespace) +// +// // later on... +// obj.logger.Info("setting foo", "value", targetValue) // // # Best Practices // @@ -112,15 +123,24 @@ limitations under the License. // may be any Go value, but how the value is formatted is determined by the // LogSink implementation. // +// Logger instances are meant to be passed around by value. Code that receives +// such a value can call its methods without having to check whether the +// instance is ready for use. +// +// Calling methods with the null logger (Logger{}) as instance will crash +// because it has no LogSink. Therefore this null logger should never be passed +// around. For cases where passing a logger is optional, a pointer to Logger +// should be used. +// // # Key Naming Conventions // // Keys are not strictly required to conform to any specification or regex, but // it is recommended that they: -// * be human-readable and meaningful (not auto-generated or simple ordinals) -// * be constant (not dependent on input data) -// * contain only printable characters -// * not contain whitespace or punctuation -// * use lower case for simple keys and lowerCamelCase for more complex ones +// - be human-readable and meaningful (not auto-generated or simple ordinals) +// - be constant (not dependent on input data) +// - contain only printable characters +// - not contain whitespace or punctuation +// - use lower case for simple keys and lowerCamelCase for more complex ones // // These guidelines help ensure that log data is processed properly regardless // of the log implementation. For example, log implementations will try to @@ -129,15 +149,14 @@ limitations under the License. // While users are generally free to use key names of their choice, it's // generally best to avoid using the following keys, as they're frequently used // by implementations: -// -// * "caller": the calling information (file/line) of a particular log line. -// * "error": the underlying error value in the `Error` method. -// * "level": the log level. -// * "logger": the name of the associated logger. -// * "msg": the log message. -// * "stacktrace": the stack trace associated with a particular log line or -// error (often from the `Error` message). -// * "ts": the timestamp for a log line. +// - "caller": the calling information (file/line) of a particular log line +// - "error": the underlying error value in the `Error` method +// - "level": the log level +// - "logger": the name of the associated logger +// - "msg": the log message +// - "stacktrace": the stack trace associated with a particular log line or +// error (often from the `Error` message) +// - "ts": the timestamp for a log line // // Implementations are encouraged to make use of these keys to represent the // above concepts, when necessary (for example, in a pure-JSON output form, it @@ -148,33 +167,36 @@ limitations under the License. // // Implementations may choose to give callers access to the underlying // logging implementation. The recommended pattern for this is: -// // Underlier exposes access to the underlying logging implementation. -// // Since callers only have a logr.Logger, they have to know which -// // implementation is in use, so this interface is less of an abstraction -// // and more of way to test type conversion. -// type Underlier interface { -// GetUnderlying() -// } +// +// // Underlier exposes access to the underlying logging implementation. +// // Since callers only have a logr.Logger, they have to know which +// // implementation is in use, so this interface is less of an abstraction +// // and more of way to test type conversion. +// type Underlier interface { +// GetUnderlying() +// } // // Logger grants access to the sink to enable type assertions like this: -// func DoSomethingWithImpl(log logr.Logger) { -// if underlier, ok := log.GetSink()(impl.Underlier) { -// implLogger := underlier.GetUnderlying() -// ... -// } -// } +// +// func DoSomethingWithImpl(log logr.Logger) { +// if underlier, ok := log.GetSink().(impl.Underlier); ok { +// implLogger := underlier.GetUnderlying() +// ... +// } +// } // // Custom `With*` functions can be implemented by copying the complete // Logger struct and replacing the sink in the copy: -// // WithFooBar changes the foobar parameter in the log sink and returns a -// // new logger with that modified sink. It does nothing for loggers where -// // the sink doesn't support that parameter. -// func WithFoobar(log logr.Logger, foobar int) logr.Logger { -// if foobarLogSink, ok := log.GetSink()(FoobarSink); ok { -// log = log.WithSink(foobarLogSink.WithFooBar(foobar)) -// } -// return log -// } +// +// // WithFooBar changes the foobar parameter in the log sink and returns a +// // new logger with that modified sink. It does nothing for loggers where +// // the sink doesn't support that parameter. +// func WithFoobar(log logr.Logger, foobar int) logr.Logger { +// if foobarLogSink, ok := log.GetSink().(FoobarSink); ok { +// log = log.WithSink(foobarLogSink.WithFooBar(foobar)) +// } +// return log +// } // // Don't use New to construct a new Logger with a LogSink retrieved from an // existing Logger. Source code attribution might not work correctly and @@ -190,11 +212,14 @@ import ( ) // New returns a new Logger instance. This is primarily used by libraries -// implementing LogSink, rather than end users. +// implementing LogSink, rather than end users. Passing a nil sink will create +// a Logger which discards all log lines. func New(sink LogSink) Logger { logger := Logger{} logger.setSink(sink) - sink.Init(runtimeInfo) + if sink != nil { + sink.Init(runtimeInfo) + } return logger } @@ -233,7 +258,7 @@ type Logger struct { // Enabled tests whether this Logger is enabled. For example, commandline // flags might be used to set the logging verbosity and disable some info logs. func (l Logger) Enabled() bool { - return l.sink.Enabled(l.level) + return l.sink != nil && l.sink.Enabled(l.level) } // Info logs a non-error message with the given key/value pairs as context. @@ -243,6 +268,9 @@ func (l Logger) Enabled() bool { // information. The key/value pairs must alternate string keys and arbitrary // values. func (l Logger) Info(msg string, keysAndValues ...interface{}) { + if l.sink == nil { + return + } if l.Enabled() { if withHelper, ok := l.sink.(CallStackHelperLogSink); ok { withHelper.GetCallStackHelper()() @@ -254,18 +282,21 @@ func (l Logger) Info(msg string, keysAndValues ...interface{}) { // Error logs an error, with the given message and key/value pairs as context. // It functions similarly to Info, but may have unique behavior, and should be // preferred for logging errors (see the package documentations for more -// information). +// information). The log message will always be emitted, regardless of +// verbosity level. // // The msg argument should be used to add context to any underlying error, // while the err argument should be used to attach the actual error that -// triggered this log line, if present. +// triggered this log line, if present. The err parameter is optional +// and nil may be passed instead of an error instance. func (l Logger) Error(err error, msg string, keysAndValues ...interface{}) { - if l.Enabled() { + if l.sink == nil { + return + } if withHelper, ok := l.sink.(CallStackHelperLogSink); ok { withHelper.GetCallStackHelper()() } l.sink.Error(err, msg, keysAndValues...) - } } // V returns a new Logger instance for a specific verbosity level, relative to @@ -273,6 +304,9 @@ func (l Logger) Error(err error, msg string, keysAndValues ...interface{}) { // level means a log message is less important. Negative V-levels are treated // as 0. func (l Logger) V(level int) Logger { + if l.sink == nil { + return l + } if level < 0 { level = 0 } @@ -283,6 +317,9 @@ func (l Logger) V(level int) Logger { // WithValues returns a new Logger instance with additional key/value pairs. // See Info for documentation on how key/value pairs work. func (l Logger) WithValues(keysAndValues ...interface{}) Logger { + if l.sink == nil { + return l + } l.setSink(l.sink.WithValues(keysAndValues...)) return l } @@ -293,6 +330,9 @@ func (l Logger) WithValues(keysAndValues ...interface{}) Logger { // contain only letters, digits, and hyphens (see the package documentation for // more information). func (l Logger) WithName(name string) Logger { + if l.sink == nil { + return l + } l.setSink(l.sink.WithName(name)) return l } @@ -313,6 +353,9 @@ func (l Logger) WithName(name string) Logger { // WithCallDepth(1) because it works with implementions that support the // CallDepthLogSink and/or CallStackHelperLogSink interfaces. func (l Logger) WithCallDepth(depth int) Logger { + if l.sink == nil { + return l + } if withCallDepth, ok := l.sink.(CallDepthLogSink); ok { l.setSink(withCallDepth.WithCallDepth(depth)) } @@ -334,6 +377,9 @@ func (l Logger) WithCallDepth(depth int) Logger { // implementation does not support either of these, the original Logger will be // returned. func (l Logger) WithCallStackHelper() (func(), Logger) { + if l.sink == nil { + return func() {}, l + } var helper func() if withCallDepth, ok := l.sink.(CallDepthLogSink); ok { l.setSink(withCallDepth.WithCallDepth(1)) @@ -346,6 +392,11 @@ func (l Logger) WithCallStackHelper() (func(), Logger) { return helper, l } +// IsZero returns true if this logger is an uninitialized zero value +func (l Logger) IsZero() bool { + return l.sink == nil +} + // contextKey is how we find Loggers in a context.Context. type contextKey struct{} @@ -431,7 +482,7 @@ type LogSink interface { WithName(name string) LogSink } -// CallDepthLogSink represents a Logger that knows how to climb the call stack +// CallDepthLogSink represents a LogSink that knows how to climb the call stack // to identify the original call site and can offset the depth by a specified // number of frames. This is useful for users who have helper functions // between the "real" call site and the actual calls to Logger methods. @@ -456,7 +507,7 @@ type CallDepthLogSink interface { WithCallDepth(depth int) LogSink } -// CallStackHelperLogSink represents a Logger that knows how to climb +// CallStackHelperLogSink represents a LogSink that knows how to climb // the call stack to identify the original call site and can skip // intermediate helper functions if they mark themselves as // helper. Go's testing package uses that approach. @@ -479,3 +530,21 @@ type CallStackHelperLogSink interface { // call site information. GetCallStackHelper() func() } + +// Marshaler is an optional interface that logged values may choose to +// implement. Loggers with structured output, such as JSON, should +// log the object return by the MarshalLog method instead of the +// original value. +type Marshaler interface { + // MarshalLog can be used to: + // - ensure that structs are not logged as strings when the original + // value has a String method: return a different type without a + // String method + // - select which fields of a complex type should get logged: + // return a simpler struct with fewer fields + // - log unexported fields: return a different struct + // with exported fields + // + // It may return any value of any type. + MarshalLog() interface{} +} diff --git a/vendor/github.com/go-logr/logr/logr_test.go b/vendor/github.com/go-logr/logr/logr_test.go index 3f638181..00ecf98e 100644 --- a/vendor/github.com/go-logr/logr/logr_test.go +++ b/vendor/github.com/go-logr/logr/logr_test.go @@ -18,6 +18,7 @@ package logr import ( "context" + "errors" "fmt" "reflect" "testing" @@ -383,8 +384,8 @@ func TestContext(t *testing.T) { } out := FromContextOrDiscard(ctx) - if _, ok := out.sink.(discardLogSink); !ok { - t.Errorf("expected a discardLogSink, got %#v", out) + if out.sink != nil { + t.Errorf("expected a nil sink, got %#v", out) } sink := &testLogSink{} @@ -400,3 +401,41 @@ func TestContext(t *testing.T) { t.Errorf("expected output to be the same as input, got in=%p, out=%p", sink, p) } } + +func TestIsZero(t *testing.T) { + var l Logger + if !l.IsZero() { + t.Errorf("expected IsZero") + } + sink := &testLogSink{} + l = New(sink) + if l.IsZero() { + t.Errorf("expected not IsZero") + } + // Discard is the same as a nil sink. + l = Discard() + if !l.IsZero() { + t.Errorf("expected IsZero") + } +} + +func TestZeroValue(t *testing.T) { + // Make sure that the zero value is useful and equivalent to a Discard logger. + var l Logger + if l.Enabled() { + t.Errorf("expected not Enabled") + } + if !l.IsZero() { + t.Errorf("expected IsZero") + } + // Make sure that none of these methods cause a crash + l.Info("foo") + l.Error(errors.New("bar"), "some error") + if l.GetSink() != nil { + t.Errorf("expected nil from GetSink") + } + l2 := l.WithName("some-name").V(2).WithValues("foo", 1).WithCallDepth(1) + l2.Info("foo") + l2.Error(errors.New("bar"), "some error") + _, _ = l.WithCallStackHelper() +} diff --git a/vendor/github.com/go-logr/logr/testing/test.go b/vendor/github.com/go-logr/logr/testing/test.go index 92954e88..fda9401f 100644 --- a/vendor/github.com/go-logr/logr/testing/test.go +++ b/vendor/github.com/go-logr/logr/testing/test.go @@ -14,102 +14,24 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package testing provides support for using logr in tests. +// Deprecated. See github.com/go-logr/logr/testr instead. package testing -import ( - "testing" - - "github.com/go-logr/logr" - "github.com/go-logr/logr/funcr" -) +import "github.com/go-logr/logr/testr" // NewTestLogger returns a logr.Logger that prints through a testing.T object. -// Info logs are only enabled at V(0). -func NewTestLogger(t *testing.T) logr.Logger { - l := &testlogger{ - Formatter: funcr.NewFormatter(funcr.Options{}), - t: t, - } - return logr.New(l) -} +// Deprecated. See github.com/go-logr/logr/testr.New instead. +var NewTestLogger = testr.New // Options carries parameters which influence the way logs are generated. -type Options struct { - // LogTimestamp tells the logger to add a "ts" key to log - // lines. This has some overhead, so some users might not want - // it. - LogTimestamp bool - - // Verbosity tells the logger which V logs to be write. - // Higher values enable more logs. - Verbosity int -} +// Deprecated. See github.com/go-logr/logr/testr.Options instead. +type Options = testr.Options // NewTestLoggerWithOptions returns a logr.Logger that prints through a testing.T object. -// In contrast to the simpler NewTestLogger, output formatting can be configured. -func NewTestLoggerWithOptions(t *testing.T, opts Options) logr.Logger { - l := &testlogger{ - Formatter: funcr.NewFormatter(funcr.Options{ - LogTimestamp: opts.LogTimestamp, - Verbosity: opts.Verbosity, - }), - t: t, - } - return logr.New(l) -} - -// Underlier exposes access to the underlying testing.T instance. Since -// callers only have a logr.Logger, they have to know which -// implementation is in use, so this interface is less of an -// abstraction and more of a way to test type conversion. -type Underlier interface { - GetUnderlying() *testing.T -} - -type testlogger struct { - funcr.Formatter - t *testing.T -} - -func (l testlogger) WithName(name string) logr.LogSink { - l.Formatter.AddName(name) - return &l -} - -func (l testlogger) WithValues(kvList ...interface{}) logr.LogSink { - l.Formatter.AddValues(kvList) - return &l -} - -func (l testlogger) GetCallStackHelper() func() { - return l.t.Helper -} - -func (l testlogger) Info(level int, msg string, kvList ...interface{}) { - prefix, args := l.FormatInfo(level, msg, kvList) - l.t.Helper() - if prefix != "" { - l.t.Logf("%s: %s", prefix, args) - } else { - l.t.Log(args) - } -} - -func (l testlogger) Error(err error, msg string, kvList ...interface{}) { - prefix, args := l.FormatError(err, msg, kvList) - l.t.Helper() - if prefix != "" { - l.t.Logf("%s: %s", prefix, args) - } else { - l.t.Log(args) - } -} - -func (l testlogger) GetUnderlying() *testing.T { - return l.t -} +// Deprecated. See github.com/go-logr/logr/testr.NewWithOptions instead. +var NewTestLoggerWithOptions = testr.NewWithOptions -// Assert conformance to the interfaces. -var _ logr.LogSink = &testlogger{} -var _ logr.CallStackHelperLogSink = &testlogger{} -var _ Underlier = &testlogger{} +// Underlier exposes access to the underlying testing.T instance. +// Deprecated. See github.com/go-logr/logr/testr.Underlier instead. +type Underlier = testr.Underlier diff --git a/vendor/github.com/go-logr/logr/testr/testr.go b/vendor/github.com/go-logr/logr/testr/testr.go new file mode 100644 index 00000000..2772b49a --- /dev/null +++ b/vendor/github.com/go-logr/logr/testr/testr.go @@ -0,0 +1,167 @@ +/* +Copyright 2019 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 testr provides support for using logr in tests. +package testr + +import ( + "testing" + + "github.com/go-logr/logr" + "github.com/go-logr/logr/funcr" +) + +// New returns a logr.Logger that prints through a testing.T object. +// Info logs are only enabled at V(0). +func New(t *testing.T) logr.Logger { + return NewWithOptions(t, Options{}) +} + +// Options carries parameters which influence the way logs are generated. +type Options struct { + // LogTimestamp tells the logger to add a "ts" key to log + // lines. This has some overhead, so some users might not want + // it. + LogTimestamp bool + + // Verbosity tells the logger which V logs to be write. + // Higher values enable more logs. + Verbosity int +} + +// NewWithOptions returns a logr.Logger that prints through a testing.T object. +// In contrast to the simpler New, output formatting can be configured. +func NewWithOptions(t *testing.T, opts Options) logr.Logger { + l := &testlogger{ + testloggerInterface: newLoggerInterfaceWithOptions(t, opts), + } + return logr.New(l) +} + +// TestingT is an interface wrapper around testing.T, testing.B and testing.F. +type TestingT interface { + Helper() + Log(args ...interface{}) +} + +// NewWithInterface returns a logr.Logger that prints through a +// TestingT object. +// In contrast to the simpler New, output formatting can be configured. +func NewWithInterface(t TestingT, opts Options) logr.Logger { + l := newLoggerInterfaceWithOptions(t, opts) + return logr.New(&l) +} + +func newLoggerInterfaceWithOptions(t TestingT, opts Options) testloggerInterface { + return testloggerInterface{ + t: t, + Formatter: funcr.NewFormatter(funcr.Options{ + LogTimestamp: opts.LogTimestamp, + Verbosity: opts.Verbosity, + }), + } +} + +// Underlier exposes access to the underlying testing.T instance. Since +// callers only have a logr.Logger, they have to know which +// implementation is in use, so this interface is less of an +// abstraction and more of a way to test type conversion. +type Underlier interface { + GetUnderlying() *testing.T +} + +// UnderlierInterface exposes access to the underlying TestingT instance. Since +// callers only have a logr.Logger, they have to know which +// implementation is in use, so this interface is less of an +// abstraction and more of a way to test type conversion. +type UnderlierInterface interface { + GetUnderlying() TestingT +} + +// Info logging implementation shared between testLogger and testLoggerInterface. +func logInfo(t TestingT, formatInfo func(int, string, []interface{}) (string, string), level int, msg string, kvList ...interface{}) { + prefix, args := formatInfo(level, msg, kvList) + t.Helper() + if prefix != "" { + args = prefix + ": " + args + } + t.Log(args) +} + +// Error logging implementation shared between testLogger and testLoggerInterface. +func logError(t TestingT, formatError func(error, string, []interface{}) (string, string), err error, msg string, kvList ...interface{}) { + prefix, args := formatError(err, msg, kvList) + t.Helper() + if prefix != "" { + args = prefix + ": " + args + } + t.Log(args) +} + +// This type exists to wrap and modify the method-set of testloggerInterface. +// In particular, it changes the GetUnderlying() method. +type testlogger struct { + testloggerInterface +} + +func (l testlogger) GetUnderlying() *testing.T { + // This method is defined on testlogger, so the only type this could + // possibly be is testing.T, even though that's not guaranteed by the type + // system itself. + return l.t.(*testing.T) //nolint:forcetypeassert +} + +type testloggerInterface struct { + funcr.Formatter + t TestingT +} + +func (l testloggerInterface) WithName(name string) logr.LogSink { + l.Formatter.AddName(name) + return &l +} + +func (l testloggerInterface) WithValues(kvList ...interface{}) logr.LogSink { + l.Formatter.AddValues(kvList) + return &l +} + +func (l testloggerInterface) GetCallStackHelper() func() { + return l.t.Helper +} + +func (l testloggerInterface) Info(level int, msg string, kvList ...interface{}) { + l.t.Helper() + logInfo(l.t, l.FormatInfo, level, msg, kvList...) +} + +func (l testloggerInterface) Error(err error, msg string, kvList ...interface{}) { + l.t.Helper() + logError(l.t, l.FormatError, err, msg, kvList...) +} + +func (l testloggerInterface) GetUnderlying() TestingT { + return l.t +} + +// Assert conformance to the interfaces. +var _ logr.LogSink = &testlogger{} +var _ logr.CallStackHelperLogSink = &testlogger{} +var _ Underlier = &testlogger{} + +var _ logr.LogSink = &testloggerInterface{} +var _ logr.CallStackHelperLogSink = &testloggerInterface{} +var _ UnderlierInterface = &testloggerInterface{} diff --git a/vendor/github.com/go-logr/logr/testr/testr_fuzz_test.go b/vendor/github.com/go-logr/logr/testr/testr_fuzz_test.go new file mode 100644 index 00000000..d1def0ef --- /dev/null +++ b/vendor/github.com/go-logr/logr/testr/testr_fuzz_test.go @@ -0,0 +1,27 @@ +//go:build go1.18 +// +build go1.18 + +/* +Copyright 2022 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 testr + +import "testing" + +func TestLoggerTestingF(t *testing.T) { + f := &testing.F{} + _ = NewWithInterface(f, Options{}) +} diff --git a/vendor/github.com/go-logr/logr/testr/testr_test.go b/vendor/github.com/go-logr/logr/testr/testr_test.go new file mode 100644 index 00000000..252c4be4 --- /dev/null +++ b/vendor/github.com/go-logr/logr/testr/testr_test.go @@ -0,0 +1,89 @@ +/* +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 testr + +import ( + "fmt" + "testing" + + "github.com/go-logr/logr" +) + +func TestLogger(t *testing.T) { + log := New(t) + log.Info("info") + log.V(0).Info("V(0).info") + log.V(1).Info("v(1).info") + log.Error(fmt.Errorf("error"), "error") + log.WithName("testing").WithValues("value", "test").Info("with prefix") + log.WithName("testing").Error(fmt.Errorf("error"), "with prefix") + Helper(log, "hello world") + + log = NewWithOptions(t, Options{ + LogTimestamp: true, + Verbosity: 1, + }) + log.V(1).Info("v(1).info with options") + + underlier, ok := log.GetSink().(Underlier) + if !ok { + t.Fatal("couldn't get underlier") + } + if t != underlier.GetUnderlying() { + t.Error("invalid underlier") + } +} + +func TestLoggerInterface(t *testing.T) { + log := NewWithInterface(t, Options{}) + log.Info("info") + log.V(0).Info("V(0).info") + log.V(1).Info("v(1).info") + log.Error(fmt.Errorf("error"), "error") + log.WithName("testing").WithValues("value", "test").Info("with prefix") + log.WithName("testing").Error(fmt.Errorf("error"), "with prefix") + Helper(log, "hello world") + + underlier, ok := log.GetSink().(UnderlierInterface) + if !ok { + t.Fatal("couldn't get underlier") + } + underlierT, ok := underlier.GetUnderlying().(*testing.T) + if !ok { + t.Fatal("couldn't get underlying *testing.T") + } + if t != underlierT { + t.Error("invalid underlier") + } +} + +func TestLoggerTestingB(t *testing.T) { + b := &testing.B{} + _ = NewWithInterface(b, Options{}) +} + +func Helper(log logr.Logger, msg string) { + helper, log := log.WithCallStackHelper() + helper() + helper2(log, msg) +} + +func helper2(log logr.Logger, msg string) { + helper, log := log.WithCallStackHelper() + helper() + log.Info(msg) +}