mirror of https://github.com/knative/caching.git
Auto-update dependencies (#209)
Produced via: `dep ensure -update knative.dev/test-infra knative.dev/pkg` /assign n3wscott /cc n3wscott
This commit is contained in:
parent
c75efc13dc
commit
9d84bba4b6
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
memory_encoder.go coverage-excluded=true
|
|
@ -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
|
|
@ -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}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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) }
|
|
@ -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')
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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)...)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue