From 9d84bba4b64cecdc8cba287a6086e6e2d36b64de Mon Sep 17 00:00:00 2001 From: Matt Moore Date: Thu, 20 Feb 2020 06:59:06 -0800 Subject: [PATCH] Auto-update dependencies (#209) Produced via: `dep ensure -update knative.dev/test-infra knative.dev/pkg` /assign n3wscott /cc n3wscott --- Gopkg.lock | 6 +- vendor/knative.dev/pkg/Gopkg.lock | 27 +- vendor/knative.dev/pkg/Gopkg.toml | 2 + vendor/knative.dev/pkg/apis/condition_set.go | 23 +- vendor/knative.dev/pkg/test/e2e_flags.go | 44 +-- .../pkg/test/logging/.gitattributes | 1 + vendor/knative.dev/pkg/test/logging/doc.go | 49 +++ vendor/knative.dev/pkg/test/logging/error.go | 91 +++++ vendor/knative.dev/pkg/test/logging/logger.go | 76 ++++ .../knative.dev/pkg/test/logging/logging.go | 109 ++++-- .../pkg/test/logging/memory_encoder.go | 74 ++++ .../pkg/test/logging/spew_encoder.go | 196 ++++++++++ vendor/knative.dev/pkg/test/logging/sugar.go | 117 ++++++ .../knative.dev/pkg/test/logging/tlogger.go | 368 ++++++++++++++++++ vendor/knative.dev/pkg/test/logging/zapr.go | 47 +++ 15 files changed, 1140 insertions(+), 90 deletions(-) create mode 100644 vendor/knative.dev/pkg/test/logging/.gitattributes create mode 100644 vendor/knative.dev/pkg/test/logging/doc.go create mode 100644 vendor/knative.dev/pkg/test/logging/error.go create mode 100644 vendor/knative.dev/pkg/test/logging/logger.go create mode 100644 vendor/knative.dev/pkg/test/logging/memory_encoder.go create mode 100644 vendor/knative.dev/pkg/test/logging/spew_encoder.go create mode 100644 vendor/knative.dev/pkg/test/logging/sugar.go create mode 100644 vendor/knative.dev/pkg/test/logging/tlogger.go create mode 100644 vendor/knative.dev/pkg/test/logging/zapr.go diff --git a/Gopkg.lock b/Gopkg.lock index bd2bfc70..63fd12ab 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -966,7 +966,7 @@ [[projects]] branch = "master" - digest = "1:4d466bcd929b5ae71fddc9153076b396c5d05479f03b2ae6318c3433578205dc" + digest = "1:1e7838d5007ae0a2f64d99f5a2c216749a03247b85b4a7d741c6ae3c4fe14e0a" name = "knative.dev/pkg" packages = [ "apis", @@ -986,7 +986,7 @@ "reconciler", ] pruneopts = "T" - revision = "5d8a01d12cc825688b14117af294139fee2270eb" + revision = "5c9bc970ce963ddad76a59b289ba90d5b118d98f" [[projects]] branch = "master" @@ -997,7 +997,7 @@ "tools/dep-collector", ] pruneopts = "UT" - revision = "fb304f6a7ac965fb3eb5cb16d5e0301ca60e3247" + revision = "359e2ba3ab51221e4a91e78f4031361306bbba0d" [[projects]] digest = "1:8730e0150dfb2b7e173890c8b9868e7a273082ef8e39f4940e3506a481cf895c" diff --git a/vendor/knative.dev/pkg/Gopkg.lock b/vendor/knative.dev/pkg/Gopkg.lock index cae75bce..2cb02a32 100644 --- a/vendor/knative.dev/pkg/Gopkg.lock +++ b/vendor/knative.dev/pkg/Gopkg.lock @@ -148,6 +148,22 @@ revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7" version = "v1.0.0" +[[projects]] + digest = "1:53becd66889185091b58ea3fc49294996f2179fb05a89702f4de7d15e581b509" + name = "github.com/go-logr/logr" + packages = ["."] + pruneopts = "NUT" + revision = "9fb12b3b21c5415d16ac18dc5cd42c1cfdd40c4e" + version = "v0.1.0" + +[[projects]] + digest = "1:340497a512995aa69c0add901d79a2096b3449d35a44a6f1f1115091a9f8c687" + name = "github.com/go-logr/zapr" + packages = ["."] + pruneopts = "NUT" + revision = "03f06a783fbb7dfaf3f629c7825480e43a7105e6" + version = "v0.1.1" + [[projects]] digest = "1:53151cc4366e3945282d4b783fd41f35222cabbc75601e68d8133648c63498d1" name = "github.com/gobuffalo/envy" @@ -1279,12 +1295,12 @@ revision = "e17681d19d3ac4837a019ece36c2a0ec31ffe985" [[projects]] - digest = "1:43099cc4ed575c40f80277c7ba7168df37d0c663bdc4f541325430bd175cce8a" + digest = "1:c0693cb981f43d82a767a3217b7640a4bdb341731d3814b38602f4e5dc4f01b3" name = "k8s.io/klog" packages = ["."] pruneopts = "NUT" - revision = "d98d8acdac006fb39831f1b25640813fef9c314f" - version = "v0.3.3" + revision = "2ca9ad30301bf30a8a6e0fa2110db6b8df699a91" + version = "v1.0.0" [[projects]] branch = "master" @@ -1355,8 +1371,9 @@ "github.com/davecgh/go-spew/spew", "github.com/evanphx/json-patch", "github.com/ghodss/yaml", + "github.com/go-logr/logr", + "github.com/go-logr/zapr", "github.com/gogo/protobuf/proto", - "github.com/golang/glog", "github.com/golang/protobuf/jsonpb", "github.com/golang/protobuf/proto", "github.com/google/go-cmp/cmp", @@ -1388,7 +1405,9 @@ "go.opencensus.io/stats/view", "go.opencensus.io/tag", "go.opencensus.io/trace", + "go.uber.org/multierr", "go.uber.org/zap", + "go.uber.org/zap/buffer", "go.uber.org/zap/zapcore", "go.uber.org/zap/zaptest", "golang.org/x/net/context", diff --git a/vendor/knative.dev/pkg/Gopkg.toml b/vendor/knative.dev/pkg/Gopkg.toml index 2f839862..201bd4f9 100644 --- a/vendor/knative.dev/pkg/Gopkg.toml +++ b/vendor/knative.dev/pkg/Gopkg.toml @@ -13,6 +13,8 @@ required = [ "knative.dev/test-infra/scripts", "knative.dev/test-infra/tools/dep-collector", "github.com/gogo/protobuf/proto", + "github.com/go-logr/logr", + "github.com/go-logr/zapr", ] [[constraint]] diff --git a/vendor/knative.dev/pkg/apis/condition_set.go b/vendor/knative.dev/pkg/apis/condition_set.go index eba01e94..f1f6827e 100644 --- a/vendor/knative.dev/pkg/apis/condition_set.go +++ b/vendor/knative.dev/pkg/apis/condition_set.go @@ -242,23 +242,38 @@ func (r conditionsImpl) ClearCondition(t ConditionType) error { // MarkTrue sets the status of t to true, and then marks the happy condition to // true if all other dependents are also true. func (r conditionsImpl) MarkTrue(t ConditionType) { - // set the specified condition + // Save the original status of t. + org := r.GetCondition(t).DeepCopy() + orgTL := r.GetTopLevelCondition().DeepCopy() + + // Set the specified condition. r.SetCondition(Condition{ Type: t, Status: corev1.ConditionTrue, Severity: r.severity(t), }) - // check the dependents. + // Check the dependents. for _, cond := range r.dependents { c := r.GetCondition(cond) - // Failed or Unknown conditions trump true conditions + // Failed or Unknown conditions trump true conditions. if !c.IsTrue() { + // Update the happy condition if the current ready condition is + // marked not ready because of this condition. + if org.Reason == orgTL.Reason && org.Message == orgTL.Message { + r.SetCondition(Condition{ + Type: r.happy, + Status: c.Status, + Reason: c.Reason, + Message: c.Message, + Severity: r.severity(r.happy), + }) + } return } } - // set the happy condition + // Set the happy condition. r.SetCondition(Condition{ Type: r.happy, Status: corev1.ConditionTrue, diff --git a/vendor/knative.dev/pkg/test/e2e_flags.go b/vendor/knative.dev/pkg/test/e2e_flags.go index a031b27d..7b022212 100644 --- a/vendor/knative.dev/pkg/test/e2e_flags.go +++ b/vendor/knative.dev/pkg/test/e2e_flags.go @@ -22,15 +22,12 @@ package test import ( "bytes" "flag" - "fmt" "os" "os/user" "path" "sync" "text/template" - _ "github.com/golang/glog" // Needed if glog and klog are to coexist - "k8s.io/klog" "knative.dev/pkg/test/logging" ) @@ -53,7 +50,6 @@ type EnvironmentFlags struct { Kubeconfig string // Path to kubeconfig (defaults to ./kube/config) Namespace string // K8s namespace (blank by default, to be overwritten by test suite) IngressEndpoint string // Host to use for ingress endpoint - LogVerbose bool // Enable verbose logging ImageTemplate string // Template to build the image reference (defaults to {{.Repository}}/{{.Name}}:{{.Tag}}) DockerRepo string // Docker repo (defaults to $KO_DOCKER_REPO) Tag string // Tag for test images @@ -83,9 +79,6 @@ func initializeFlags() *EnvironmentFlags { flag.StringVar(&f.IngressEndpoint, "ingressendpoint", "", "Provide a static endpoint url to the ingress server used during tests.") - flag.BoolVar(&f.LogVerbose, "logverbose", false, - "Set this flag to true if you would like to see verbose logging.") - flag.StringVar(&f.ImageTemplate, "imagetemplate", "{{.Repository}}/{{.Name}}:{{.Tag}}", "Provide a template to generate the reference to an image from the test. Defaults to `{{.Repository}}/{{.Name}}:{{.Tag}}`.") @@ -95,45 +88,12 @@ func initializeFlags() *EnvironmentFlags { flag.StringVar(&f.Tag, "tag", "latest", "Provide the version tag for the test images.") - klog.InitFlags(klogFlags) - flag.Set("v", klogDefaultLogLevel) - flag.Set("alsologtostderr", "true") - return &f } -func printFlags() { - fmt.Print("Test Flags: {") - flag.CommandLine.VisitAll(func(f *flag.Flag) { - fmt.Printf("'%s': '%s', ", f.Name, f.Value.String()) - }) - fmt.Println("}") -} - -// SetupLoggingFlags initializes the logging libraries at runtime +// TODO(coryrc): Remove once other repos are moved to call logging.InitializeLogger() directly func SetupLoggingFlags() { - flagsSetupOnce.Do(func() { - // Sync the glog flags to klog - flag.CommandLine.VisitAll(func(f1 *flag.Flag) { - f2 := klogFlags.Lookup(f1.Name) - if f2 != nil { - value := f1.Value.String() - f2.Value.Set(value) - } - }) - if Flags.LogVerbose { - // If klog verbosity is not set to a non-default value (via "-args -v=X"), - if flag.CommandLine.Lookup("v").Value.String() == klogDefaultLogLevel { - // set up verbosity for klog so round_trippers.go prints: - // URL, request headers, response headers, and partial response body - // See levels in vendor/k8s.io/client-go/transport/round_trippers.go:DebugWrappers for other options - klogFlags.Set("v", "8") - flag.Set("v", "8") // This is for glog, since glog=>klog sync is one-time - } - printFlags() - } - logging.InitializeLogger(Flags.LogVerbose) - }) + logging.InitializeLogger() } // ImagePath is a helper function to transform an image name into an image reference that can be pulled. diff --git a/vendor/knative.dev/pkg/test/logging/.gitattributes b/vendor/knative.dev/pkg/test/logging/.gitattributes new file mode 100644 index 00000000..19c29c12 --- /dev/null +++ b/vendor/knative.dev/pkg/test/logging/.gitattributes @@ -0,0 +1 @@ +memory_encoder.go coverage-excluded=true diff --git a/vendor/knative.dev/pkg/test/logging/doc.go b/vendor/knative.dev/pkg/test/logging/doc.go new file mode 100644 index 00000000..e76b9cbe --- /dev/null +++ b/vendor/knative.dev/pkg/test/logging/doc.go @@ -0,0 +1,49 @@ +/* +Copyright 2020 The Knative 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 logging assists setting up test logging and using leveled logging in tests. + +The TLogger is designed to assist the test writer in creating more useful tests and +collecting log data in multiple streams, optimizing for human readability in one and +machine readability in another. It's designed to mimic the testing.T object rather closely and +use Zap logging semantics, both things already in use in Knative, to minimize the time developers +need to spend learning the tool. + +Inspired by and uses go-logr. + +Advantages + +The TLogger enhances test design through subtle nudges and affordances: + +* It encourages only logging with .V(), giving the writer a nudge to think about how important it is, +but without requiring them to fit it in a narrowly-defined category. + +* Reduces boilerplate of carrying around context for errors in several different variables, +using .WithValues(), which results in more consistent and reusable code across the tests. + +Porting + +To port code from using testing.T to logging.TLogger, the interfaces knative.dev/pkg/test.T and +knative.dev/pkg/test.TLegacy have been created. All library functions should be refactored to use +one interface and all .Log() calls rewritten to use structured format, which works with testing and +TLogger. If a library function needs test functions not available even in test.TLegacy, +it's probably badly written. + +Then any test can be incrementally rewritten to use TLogger, as it coexists with testing.T without issue. + +*/ +package logging diff --git a/vendor/knative.dev/pkg/test/logging/error.go b/vendor/knative.dev/pkg/test/logging/error.go new file mode 100644 index 00000000..d3caea91 --- /dev/null +++ b/vendor/knative.dev/pkg/test/logging/error.go @@ -0,0 +1,91 @@ +/* +Copyright 2020 The Knative 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 logging + +import ( + "fmt" + + "github.com/davecgh/go-spew/spew" +) + +// StructuredError is an error which can hold arbitrary key-value arguments. +// +// TODO(coryrc): The Structured Error is experimental and likely to be removed, but is currently in use in a refactored test. +type StructuredError interface { + error + GetValues() []interface{} + WithValues(...interface{}) StructuredError + DisableValuePrinting() + EnableValuePrinting() +} + +type structuredError struct { + msg string + keysAndValues []interface{} + print bool +} + +func keysAndValuesToSpewedMap(args ...interface{}) map[string]string { + m := make(map[string]string, len(args)/2) + for i := 0; i < len(args); i += 2 { + key, val := args[i], args[i+1] + if keyStr, ok := key.(string); ok { + m[keyStr] = spew.Sdump(val) + } + } + return m +} + +// Implement `error` interface +func (e structuredError) Error() string { + // TODO(coryrc): accept zap.Field entries? + if e.print { + // %v for fmt.Sprintf does print keys sorted + return fmt.Sprintf("Error: %s\nContext:\n%v", e.msg, keysAndValuesToSpewedMap(e.keysAndValues...)) + } else { + return e.msg + } +} + +// GetValues gives you the structured key values in a plist +func (e structuredError) GetValues() []interface{} { + return e.keysAndValues +} + +// DisableValuePrinting disables printing out the keys and values from the Error() method +func (e *structuredError) DisableValuePrinting() { + e.print = false +} + +// EnableValuePrinting enables printing out the keys and values from the Error() method +func (e *structuredError) EnableValuePrinting() { + e.print = true +} + +// Create a StructuredError. Gives a little better logging when given to a TLogger. +// This may prove to not be useful if users use the logger's WithValues() better. +func Error(msg string, keysAndValues ...interface{}) *structuredError { + return &structuredError{msg, keysAndValues, true} +} + +// WithValues operates just like TLogger's WithValues but stores them in the error object. +func (e *structuredError) WithValues(keysAndValues ...interface{}) StructuredError { + newKAV := make([]interface{}, 0, len(keysAndValues)+len(e.keysAndValues)) + newKAV = append(newKAV, e.keysAndValues...) + newKAV = append(newKAV, keysAndValues...) + return &structuredError{e.msg, newKAV, e.print} +} diff --git a/vendor/knative.dev/pkg/test/logging/logger.go b/vendor/knative.dev/pkg/test/logging/logger.go new file mode 100644 index 00000000..b8f3e4a7 --- /dev/null +++ b/vendor/knative.dev/pkg/test/logging/logger.go @@ -0,0 +1,76 @@ +// Copyright 2020 The Knative Authors +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Copying testingWriter from zaptest and allowing it to be disabled + +package logging + +import ( + "bytes" + "errors" + "testing" +) + +// testingWriter is a WriteSyncer that writes to the given testing.TB. +type testingWriter struct { + t *testing.T + + // If true, the test will be marked as failed if this testingWriter is + // ever used. + markFailed bool +} + +func newTestingWriter(t *testing.T) testingWriter { + return testingWriter{t: t} +} + +// WithMarkFailed returns a copy of this testingWriter with markFailed set to +// the provided value. +func (w testingWriter) WithMarkFailed(v bool) testingWriter { + w.markFailed = v + return w +} + +func (w testingWriter) Write(p []byte) (n int, err error) { + if w.t == nil { + return 0, errors.New("Write to buffer after test function completed") + } + n = len(p) + + // Strip trailing newline because t.Log always adds one. + p = bytes.TrimRight(p, "\n") + + // Note: t.Log is safe for concurrent use. + w.t.Logf("%s", p) + if w.markFailed { + w.t.Fail() + } + + return n, nil +} + +func (w testingWriter) Sync() error { + return nil +} + +func (w *testingWriter) Disable() { + w.t = nil +} diff --git a/vendor/knative.dev/pkg/test/logging/logging.go b/vendor/knative.dev/pkg/test/logging/logging.go index 74ef1d24..f3b2c5dd 100644 --- a/vendor/knative.dev/pkg/test/logging/logging.go +++ b/vendor/knative.dev/pkg/test/logging/logging.go @@ -21,15 +21,17 @@ package logging import ( "context" - "fmt" + "flag" + "os" "strings" + "sync" "time" "github.com/davecgh/go-spew/spew" "go.opencensus.io/stats/view" "go.opencensus.io/trace" "go.uber.org/zap" - "knative.dev/pkg/logging" + "go.uber.org/zap/zapcore" ) const ( @@ -44,8 +46,6 @@ const ( // FormatLogger is a printf style function for logging in tests. type FormatLogger func(template string, args ...interface{}) -var logger *zap.SugaredLogger - var exporter *zapMetricExporter // zapMetricExporter is a stats and trace exporter that logs the @@ -80,29 +80,22 @@ func (e *zapMetricExporter) ExportSpan(vd *trace.SpanData) { } } -func newLogger(logLevel string) *zap.SugaredLogger { - configJSONTemplate := `{ - "level": "%s", - "encoding": "console", - "outputPaths": ["stdout"], - "errorOutputPaths": ["stderr"], - "encoderConfig": { - "timeKey": "ts", - "messageKey": "message", - "levelKey": "level", - "nameKey": "logger", - "callerKey": "caller", - "messageKey": "msg", - "stacktraceKey": "stacktrace", - "lineEnding": "", - "levelEncoder": "", - "timeEncoder": "iso8601", - "durationEncoder": "", - "callerEncoder": "" - } - }` - configJSON := fmt.Sprintf(configJSONTemplate, logLevel) - l, _ := logging.NewLogger(string(configJSON), logLevel, zap.AddCallerSkip(1)) +const ( + logrZapDebugLevel = 3 +) + +func zapLevelFromLogrLevel(logrLevel int) zapcore.Level { + // Zap levels are -1, 0, 1, 2,... corresponding to DebugLevel, InfoLevel, WarnLevel, ErrorLevel,... + // zapr library just does zapLevel := -1*logrLevel; which means: + // 1. Info level is only active at 0 (versus 2 in klog being generally equivalent to Info) + // 2. Only verbosity of 0 and 1 map to valid Zap levels + // According to https://github.com/uber-go/zap/issues/713 custom levels (i.e. < -1) aren't guaranteed to work, so not using them (for now). + + l := zap.InfoLevel + if logrLevel >= logrZapDebugLevel { + l = zap.DebugLevel + } + return l } @@ -115,9 +108,9 @@ func InitializeMetricExporter(context string) { trace.UnregisterExporter(exporter) } - logger := logger.Named(context) + l := logger.Named(context).Sugar() - exporter = &zapMetricExporter{logger: logger} + exporter = &zapMetricExporter{logger: l} view.RegisterExporter(exporter) trace.RegisterExporter(exporter) @@ -125,12 +118,54 @@ func InitializeMetricExporter(context string) { trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()}) } -// InitializeLogger initializes the base logger -func InitializeLogger(logVerbose bool) { - logLevel := "info" - if logVerbose { - logLevel = "debug" - } - - logger = newLogger(logLevel) +func printFlags() { + flagList := make([]interface{}, 0) + flag.CommandLine.VisitAll(func(f *flag.Flag) { + flagList = append(flagList, f.Name, f.Value.String()) + }) + logger.Sugar().Debugw("Test Flags", flagList...) +} + +var ( + zapCore zapcore.Core + logger *zap.Logger + verbosity int // Amount of log verbosity + loggerInitializeOnce = &sync.Once{} +) + +// InitializeLogger initializes logging for Knative tests. +// It should be called prior to executing tests but after command-line flags have been processed. +// Recommend doing it in a TestMain(). +func InitializeLogger() { + loggerInitializeOnce.Do(func() { + humanEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()) + + // Output streams + // TODO(coryrc): also open a log file if in Prow? + stdOut := zapcore.Lock(os.Stdout) + + // Level function helper + zapLevel := zapLevelFromLogrLevel(verbosity) + isPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { + return lvl >= zapLevel + }) + + // Assemble the output streams + zapCore = zapcore.NewTee( + // TODO(coryrc): log JSON output somewhere? + zapcore.NewCore(humanEncoder, stdOut, isPriority), + ) + + logger = zap.New(zapCore) + zap.ReplaceGlobals(logger) // Gets used by klog/glog proxy libraries + + if verbosity > 2 { + printFlags() + } + }) +} + +func init() { + flag.IntVar(&verbosity, "verbosity", 2, + "Amount of verbosity, 0-10. See https://github.com/go-logr/logr#how-do-i-choose-my-v-levels and https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md") } diff --git a/vendor/knative.dev/pkg/test/logging/memory_encoder.go b/vendor/knative.dev/pkg/test/logging/memory_encoder.go new file mode 100644 index 00000000..248046b6 --- /dev/null +++ b/vendor/knative.dev/pkg/test/logging/memory_encoder.go @@ -0,0 +1,74 @@ +// Copyright 2020 The Knative Authors +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package logging + +import ( + "time" + + "go.uber.org/zap/zapcore" +) + +// sliceArrayEncoder is an ArrayEncoder backed by a simple []interface{}. Like +// the MapObjectEncoder, it's not designed for production use. +type sliceArrayEncoder struct { + elems []interface{} +} + +func (s *sliceArrayEncoder) AppendArray(v zapcore.ArrayMarshaler) error { + enc := &sliceArrayEncoder{} + err := v.MarshalLogArray(enc) + s.elems = append(s.elems, enc.elems) + return err +} + +func (s *sliceArrayEncoder) AppendObject(v zapcore.ObjectMarshaler) error { + m := zapcore.NewMapObjectEncoder() + err := v.MarshalLogObject(m) + s.elems = append(s.elems, m.Fields) + return err +} + +func (s *sliceArrayEncoder) AppendReflected(v interface{}) error { + s.elems = append(s.elems, v) + return nil +} + +func (s *sliceArrayEncoder) AppendBool(v bool) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendByteString(v []byte) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendComplex128(v complex128) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendComplex64(v complex64) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendDuration(v time.Duration) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendFloat64(v float64) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendFloat32(v float32) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendInt(v int) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendInt64(v int64) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendInt32(v int32) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendInt16(v int16) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendInt8(v int8) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendString(v string) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendTime(v time.Time) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendUint(v uint) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendUint64(v uint64) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendUint32(v uint32) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendUint16(v uint16) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendUint8(v uint8) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendUintptr(v uintptr) { s.elems = append(s.elems, v) } diff --git a/vendor/knative.dev/pkg/test/logging/spew_encoder.go b/vendor/knative.dev/pkg/test/logging/spew_encoder.go new file mode 100644 index 00000000..7a7f5768 --- /dev/null +++ b/vendor/knative.dev/pkg/test/logging/spew_encoder.go @@ -0,0 +1,196 @@ +// Copyright 2020 The Knative Authors +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package logging + +import ( + "fmt" + "sort" + "strings" + "sync" + + "go.uber.org/zap" + "go.uber.org/zap/buffer" + . "go.uber.org/zap/zapcore" +) + +var ( + _pool = buffer.NewPool() + _sliceEncoderPool = sync.Pool{ + New: func() interface{} { + return &sliceArrayEncoder{elems: make([]interface{}, 0, 2)} + }, + } +) + +func init() { + zap.RegisterEncoder("spew", func(encoderConfig EncoderConfig) (Encoder, error) { + return NewSpewEncoder(encoderConfig), nil + }) +} + +// NewSpewEncoder encodes logs using the spew library. +// +// The JSON encoder (also used by the console encoder) included in Zap can only print objects that +// can be serialized to JSON and doesn't print them in the most readable way. This spew encoder is +// designed to make human-readable log only and get the most information to the user on any data type. +// +// Code is mostly from console_encoder.go in zapcore. +func NewSpewEncoder(cfg EncoderConfig) *SpewEncoder { + enc := SpewEncoder{} + enc.MapObjectEncoder = NewMapObjectEncoder() + enc.EncoderConfig = &cfg + return &enc +} + +// SpewEncoder implements zapcore.Encoder interface +type SpewEncoder struct { + *MapObjectEncoder + *EncoderConfig +} + +// Implements zapcore.Encoder interface +func (enc *SpewEncoder) Clone() Encoder { + n := NewSpewEncoder(*(enc.EncoderConfig)) + for k, v := range enc.Fields { + n.Fields[k] = v + } + return n +} + +func getSliceEncoder() *sliceArrayEncoder { + return _sliceEncoderPool.Get().(*sliceArrayEncoder) +} + +func putSliceEncoder(e *sliceArrayEncoder) { + e.elems = e.elems[:0] + _sliceEncoderPool.Put(e) +} + +// Implements zapcore.Encoder interface. +func (enc *SpewEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, error) { + line := _pool.Get() + + // Could probably rewrite this portion and remove the copied + // memory_encoder.go from this folder + arr := getSliceEncoder() + defer putSliceEncoder(arr) + + if enc.TimeKey != "" && enc.EncodeTime != nil { + enc.EncodeTime(ent.Time, arr) + } + if enc.LevelKey != "" && enc.EncodeLevel != nil { + enc.EncodeLevel(ent.Level, arr) + } + + if ent.LoggerName != "" && enc.NameKey != "" { + nameEncoder := enc.EncodeName + + if nameEncoder == nil { + // Fall back to FullNameEncoder for backward compatibility. + nameEncoder = FullNameEncoder + } + + nameEncoder(ent.LoggerName, arr) + } + if ent.Caller.Defined && enc.CallerKey != "" && enc.EncodeCaller != nil { + enc.EncodeCaller(ent.Caller, arr) + } + for i := range arr.elems { + if i > 0 { + line.AppendByte('\t') + } + fmt.Fprint(line, arr.elems[i]) + } + + // Add the message itself. + if enc.MessageKey != "" { + enc.addTabIfNecessary(line) + line.AppendString(ent.Message) + } + + // Add any structured context. + enc.writeContext(line, fields) + + // If there's no stacktrace key, honor that; this allows users to force + // single-line output. + if ent.Stack != "" && enc.StacktraceKey != "" { + line.AppendByte('\n') + line.AppendString(ent.Stack) + } + + if enc.LineEnding != "" { + line.AppendString(enc.LineEnding) + } else { + line.AppendString(DefaultLineEnding) + } + return line, nil +} + +func (enc *SpewEncoder) writeContext(line *buffer.Buffer, extra []Field) { + if len(extra) == 0 && len(enc.Fields) == 0 { + return + } + + // This could probably be more efficient, but .AddTo() is convenient + + context := NewMapObjectEncoder() + for k, v := range enc.Fields { + context.Fields[k] = v + } + for i := range extra { + extra[i].AddTo(context) + } + + enc.addTabIfNecessary(line) + line.AppendString("\nContext:\n") + var keys []string + for k := range context.Fields { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + line.AppendString(k) + line.AppendString(": ") + line.AppendString(stringify(context.Fields[k])) + line.TrimNewline() + line.AppendString("\n") + } +} + +func stringify(a interface{}) string { + s, ok := a.(string) + if !ok { + s = strings.TrimSuffix(spewConfig.Sdump(a), "\n") + } + ret := strings.ReplaceAll(s, "\n", "\n ") + hasNewlines := s != ret + if hasNewlines { + return "\n " + ret + } + return s +} + +func (enc *SpewEncoder) addTabIfNecessary(line *buffer.Buffer) { + if line.Len() > 0 { + line.AppendByte('\t') + } +} diff --git a/vendor/knative.dev/pkg/test/logging/sugar.go b/vendor/knative.dev/pkg/test/logging/sugar.go new file mode 100644 index 00000000..e03d1d4e --- /dev/null +++ b/vendor/knative.dev/pkg/test/logging/sugar.go @@ -0,0 +1,117 @@ +// Copyright 2020 Knative Authors +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package logging + +import ( + "github.com/davecgh/go-spew/spew" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "go.uber.org/multierr" +) + +const ( + _oddNumberErrMsg = "Ignored key without a value." + _nonStringKeyErrMsg = "Ignored key-value pairs with non-string keys." + spewLevel1 = 2 + spewLevel2 = 4 + spewLevel3 = 6 +) + +var spewConfig *spew.ConfigState + +func init() { + spewConfig = spew.NewDefaultConfig() + spewConfig.DisableCapacities = true + spewConfig.SortKeys = true + spewConfig.SpewKeys = true + spewConfig.ContinueOnMethod = true +} + +func (o *TLogger) handleFields(args []interface{}) []zap.Field { + if len(args) == 0 { + return nil + } + s := o.l.Sugar() + + // Allocate enough space for the worst case; if users pass only structured + // fields, we shouldn't penalize them with extra allocations. + fields := make([]zap.Field, 0, len(args)) + var invalid invalidPairs + + for i := 0; i < len(args); { + // This is a strongly-typed field. Consume it and move on. + if f, ok := args[i].(zap.Field); ok { + fields = append(fields, f) + i++ + continue + } + + // Make sure this element isn't a dangling key. + if i == len(args)-1 { + s.DPanic(_oddNumberErrMsg, zap.Any("ignored", args[i])) + break + } + + // Consume this value and the next, treating them as a key-value pair. If the + // key isn't a string, add this pair to the slice of invalid pairs. + key, val := args[i], args[i+1] + if keyStr, ok := key.(string); !ok { + // Subsequent errors are likely, so allocate once up front. + if cap(invalid) == 0 { + invalid = make(invalidPairs, 0, len(args)/2) + } + invalid = append(invalid, invalidPair{i, key, val}) + } else { + fields = append(fields, zap.Any(keyStr, val)) + } + i += 2 + } + + // If we encountered any invalid key-value pairs, log an error. + if len(invalid) > 0 { + s.DPanic(_nonStringKeyErrMsg, zap.Array("invalid", invalid), zap.String("all_input", spew.Sprintf("%#+v", args))) + } + return fields +} + +type invalidPair struct { + position int + key, value interface{} +} + +func (p invalidPair) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddInt64("position", int64(p.position)) + zap.Any("key", p.key).AddTo(enc) + zap.Any("value", p.value).AddTo(enc) + return nil +} + +type invalidPairs []invalidPair + +func (ps invalidPairs) MarshalLogArray(enc zapcore.ArrayEncoder) error { + var err error + for i := range ps { + err = multierr.Append(err, enc.AppendObject(ps[i])) + } + return err +} diff --git a/vendor/knative.dev/pkg/test/logging/tlogger.go b/vendor/knative.dev/pkg/test/logging/tlogger.go new file mode 100644 index 00000000..29f0948d --- /dev/null +++ b/vendor/knative.dev/pkg/test/logging/tlogger.go @@ -0,0 +1,368 @@ +/* +Copyright 2020 The Knative 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 logging + +import ( + "errors" + "fmt" + "testing" + + "github.com/go-logr/logr" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// TLogger is TLogger +type TLogger struct { + l *zap.Logger + level int + t *testing.T + errs map[string][]interface{} // For Collect() + dontFail bool +} + +// V() returns an InfoLogger from go-logr. +// +// This should be the main way your tests log. +// Most frequent usage is used directly: +// t.V(2).Info("Something at regular level") +// But if something computationally difficult is to be done, can do: +// if l := t.V(8); l.Enabled() { +// x := somethingExpensive() +// l.Info("logging it", "expensiveThing", x) +// } +// +// Elsewhere in this documentation refers to a hypothetical .V(errorLevel) to simplify explanations. +// The V() function cannot write to the error level; the Error, ErrorIfErr, Fatal, and +// FatalIfErr methods are the only way to write to the error level. +func (o *TLogger) V(level int) logr.InfoLogger { + // Consider adding || (level <= logrZapDebugLevel && o.l.Core().Enabled(zapLevelFromLogrLevel(level))) + // Reason to add it is even if you ask for verbosity=1, in case of error you'll get up to verbosity=3 in the debug output + // but since zapTest uses Debug, you always get V(<=3) even when verbosity < 3 + // Probable solution is to write to t.Log at Info level? + if level <= o.level { + return &infoLogger{ + logrLevel: o.level, + t: o, + } + } + return disabledInfoLogger +} + +// WithValues() acts like Zap's With() method. +// Consistent with logr.Logger.WithValues() +// Whenever anything is logged with the returned TLogger, +// it will act as if these keys and values were passed into every logging call. +func (o *TLogger) WithValues(keysAndValues ...interface{}) *TLogger { + return o.cloneWithNewLogger(o.l.With(o.handleFields(keysAndValues)...)) +} + +// WithName() acts like Zap's Named() method. +// Consistent with logr.Logger.WithName() +// Appends the name onto the current logger +func (o *TLogger) WithName(name string) *TLogger { + return o.cloneWithNewLogger(o.l.Named(name)) +} + +// Custom additions: + +// ErrorIfErr fails the current test if the err != nil. +// Remaining arguments function as if passed to .V(errorLevel).Info() (were that a thing) +// Same signature as logr.Logger.Error() method, but as this is a test, it functions slightly differently. +func (o *TLogger) ErrorIfErr(err error, msg string, keysAndValues ...interface{}) { + if err != nil { + o.error(err, msg, keysAndValues) + o.fail() + } +} + +// FatalIfErr is just like ErrorIfErr() but test execution stops immediately +func (o *TLogger) FatalIfErr(err error, msg string, keysAndValues ...interface{}) { + if err != nil { + o.error(err, msg, keysAndValues) + o.failNow() + } +} + +// Error is essentially a .V(errorLevel).Info() followed by failing the test. +// Intended usage is Error(msg string, key-value alternating arguments) +// Same effect as testing.T.Error +// Generic definition for compatibility with test.T interface +// Implements test.T +func (o *TLogger) Error(stringThenKeysAndValues ...interface{}) { + // Using o.error to have consistent call depth for Error, FatalIfErr, Info, etc + o.error(o.errorWithRuntimeCheck(stringThenKeysAndValues...)) + o.fail() +} + +// Fatal is essentially a .V(errorLevel).Info() followed by failing and immediately stopping the test. +// Intended usage is Fatal(msg string, key-value alternating arguments) +// Same effect as testing.T.Fatal +// Generic definition for compatibility with test.TLegacy interface +// Implements test.TLegacy +func (o *TLogger) Fatal(stringThenKeysAndValues ...interface{}) { + o.error(o.errorWithRuntimeCheck(stringThenKeysAndValues...)) + o.failNow() +} + +func (o *TLogger) fail() { + if o.t != nil && !o.dontFail { + o.t.Fail() + } +} + +func (o *TLogger) failNow() { + if o.t != nil && !o.dontFail { + o.t.FailNow() + } +} + +func validateKeysAndValues(keysAndValues ...interface{}) bool { + length := len(keysAndValues) + for i := 0; i < length; { + _, isField := keysAndValues[i].(zapcore.Field) + _, isString := keysAndValues[i].(string) + if isField { + i += 1 + } else if isString { + if i == length-1 { + return false + } + i += 2 + } else { + return false + } + } + return true +} + +func (o *TLogger) interfacesToFields(things ...interface{}) []interface{} { + o.V(5).Info("DEPRECATED Error/Fatal usage", zap.Stack("callstack")) + fields := make([]interface{}, 2*len(things)) + for i, d := range things { + fields[i*2] = fmt.Sprintf("arg %d", i) + fields[i*2+1] = d + } + return fields +} + +func (o *TLogger) errorWithRuntimeCheck(stringThenKeysAndValues ...interface{}) (error, string, []interface{}) { + if len(stringThenKeysAndValues) == 0 { + return nil, "", nil + } else { + s, isString := stringThenKeysAndValues[0].(string) + e, isError := stringThenKeysAndValues[0].(error) + if isString { + // Desired case (hopefully) + remainder := stringThenKeysAndValues[1:] + if !validateKeysAndValues(remainder...) { + remainder = o.interfacesToFields(remainder...) + } + return nil, s, remainder + } else if isError && len(stringThenKeysAndValues) == 1 { + return e, "", nil + } else { + return nil, "unstructured error", o.interfacesToFields(stringThenKeysAndValues...) + } + } +} + +// Run a subtest. Just like testing.T.Run but creates a TLogger. +func (o *TLogger) Run(name string, f func(t *TLogger)) { + tfunc := func(ts *testing.T) { + tl, cancel := newTLogger(ts, o.level, o.dontFail) + defer cancel() + f(tl) + } + o.t.Run(name, tfunc) +} + +// Name is just like testing.T.Name() +// Implements test.T +func (o *TLogger) Name() string { + return o.t.Name() +} + +// Helper cannot work as an indirect call, so just do nothing :( +// Implements test.T +func (o *TLogger) Helper() { +} + +// SkipNow immediately stops test execution +// Implements test.T +func (o *TLogger) SkipNow() { + o.t.SkipNow() +} + +// Log is deprecated: only existing for test.T compatibility +// Please use leveled logging via .V().Info() +// Will panic if given data incompatible with Info() function +// Implements test.T +func (o *TLogger) Log(args ...interface{}) { + // This is complicated to ensure exactly 2 levels of indirection + i := o.V(2) + iL, ok := i.(*infoLogger) + if ok { + iL.indirectWrite(args[0].(string), args[1:]...) + } +} + +// Parallel allows tests or subtests to run in parallel +// Just calls the testing.T.Parallel() under the hood +func (o *TLogger) Parallel() { + o.t.Parallel() +} + +// Logf is deprecated: only existing for test.TLegacy compatibility +// Please use leveled logging via .V().Info() +// Implements test.TLegacy +func (o *TLogger) Logf(fmtS string, args ...interface{}) { + // This is complicated to ensure exactly 2 levels of indirection + iL, ok := o.V(2).(*infoLogger) + if ok { + iL.indirectWrite(fmt.Sprintf(fmtS, args...)) + } +} + +func (o *TLogger) error(err error, msg string, keysAndValues []interface{}) { + var newKAV []interface{} + var serr StructuredError + if errors.As(err, &serr) { + serr.DisableValuePrinting() + defer serr.EnableValuePrinting() + newLen := len(keysAndValues) + len(serr.GetValues()) + newKAV = make([]interface{}, 0, newLen+2) + newKAV = append(newKAV, keysAndValues...) + newKAV = append(newKAV, serr.GetValues()...) + } + if err != nil { + if msg == "" { // This is used if just the error is given to .Error() or .Fatal() + msg = err.Error() + } else { + if newKAV == nil { + newKAV = make([]interface{}, 0, len(keysAndValues)+1) + newKAV = append(newKAV, keysAndValues...) + } + newKAV = append(newKAV, zap.Error(err)) + } + } + if newKAV != nil { + keysAndValues = newKAV + } + if checkedEntry := o.l.Check(zap.ErrorLevel, msg); checkedEntry != nil { + checkedEntry.Write(o.handleFields(keysAndValues)...) + } +} + +// Creation and Teardown + +// Create a TLogger object using the global Zap logger and the current testing.T +// `defer` a call to second return value immediately after. +func NewTLogger(t *testing.T) (*TLogger, func()) { + return newTLogger(t, verbosity, false) +} + +func newTLogger(t *testing.T, verbosity int, dontFail bool) (*TLogger, func()) { + testOptions := []zap.Option{ + zap.AddCaller(), + zap.AddCallerSkip(2), + zap.Development(), + } + writer := newTestingWriter(t) + // Based off zap.NewDevelopmentEncoderConfig() + cfg := zapcore.EncoderConfig{ + // Wanted keys can be anything except the empty string. + TimeKey: "", + LevelKey: "", + NameKey: "", + CallerKey: "C", + MessageKey: "M", + StacktraceKey: "S", + LineEnding: zapcore.DefaultLineEnding, + EncodeLevel: zapcore.CapitalLevelEncoder, + EncodeTime: zapcore.ISO8601TimeEncoder, + EncodeDuration: zapcore.StringDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + } + core := zapcore.NewCore( + NewSpewEncoder(cfg), + writer, + zapcore.DebugLevel, + ) + if zapCore != nil { + core = zapcore.NewTee( + zapCore, + core, + // TODO(coryrc): Open new file (maybe creating JUnit!?) with test output? + ) + } + log := zap.New(core, testOptions...).Named(t.Name()) + tlogger := TLogger{ + l: log, + level: verbosity, + t: t, + errs: make(map[string][]interface{}, 0), + dontFail: dontFail, + } + return &tlogger, func() { + tlogger.handleCollectedErrors() + // Sometimes goroutines exist after a test and they cause panics if they attempt to call t.Log(). + // Prevent this panic by disabling writes to the testing.T (we'll still get them everywhere else). + writer.Disable() + } +} + +func (o *TLogger) cloneWithNewLogger(l *zap.Logger) *TLogger { + t := TLogger{ + l: l, + level: o.level, + t: o.t, + errs: o.errs, + dontFail: o.dontFail, + } + return &t +} + +// Collect allows you to commingle multiple validations during one test execution. +// Under the hood, it creates a sub-test during cleanup and iterates through the collected values, printing them. +// If any are errors, it fails the subtest. +// Currently experimental and likely to be removed +func (o *TLogger) Collect(key string, value interface{}) { + list, has_key := o.errs[key] + if has_key { + list = append(list, value) + } else { + list = make([]interface{}, 1) + list[0] = value + } + o.errs[key] = list +} + +func (o *TLogger) handleCollectedErrors() { + for name, list := range o.errs { + o.Run(name, func(t *TLogger) { + for _, item := range list { + _, isError := item.(error) + if isError { + t.Error(item) + } else { + t.V(3).Info(spewConfig.Sprint(item)) + } + } + }) + } +} diff --git a/vendor/knative.dev/pkg/test/logging/zapr.go b/vendor/knative.dev/pkg/test/logging/zapr.go new file mode 100644 index 00000000..c0b9f585 --- /dev/null +++ b/vendor/knative.dev/pkg/test/logging/zapr.go @@ -0,0 +1,47 @@ +// Copyright 2020 Knative Authors +// Copyright 2018 Solly Ross +// +// 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. + +// The useful parts of this file have been preserved +// from their origin at https://github.com/go-logr/zapr/tree/8f2487342d52a33a1793e50e3ca04bc1767aa65c + +package logging + +// noopInfoLogger is a logr.InfoLogger that's always disabled, and does nothing. +type noopInfoLogger struct{} + +func (l *noopInfoLogger) Enabled() bool { return false } +func (l *noopInfoLogger) Info(_ string, _ ...interface{}) {} + +var disabledInfoLogger = &noopInfoLogger{} + +// infoLogger is a logr.InfoLogger that uses Zap to log at a particular +// level. +type infoLogger struct { + logrLevel int + t *TLogger +} + +func (i *infoLogger) Enabled() bool { return true } +func (i *infoLogger) Info(msg string, keysAndVals ...interface{}) { + i.indirectWrite(msg, keysAndVals...) +} + +// This function just exists to have consistent 2-level call depth for Zap proxying +func (i *infoLogger) indirectWrite(msg string, keysAndVals ...interface{}) { + lvl := zapLevelFromLogrLevel(i.logrLevel) + if checkedEntry := i.t.l.Check(lvl, msg); checkedEntry != nil { + checkedEntry.Write(i.t.handleFields(keysAndVals)...) + } +}