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.


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.


To port code from using testing.T to logging.TLogger, the interfaces and 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.



This section is empty.


This section is empty.


func Error

func Error(msg string, keysAndValues ...interface{}) error

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 GetEmitableSpan

func GetEmitableSpan(ctx context.Context, metricName string) *trace.Span

GetEmitableSpan starts and returns a trace.Span with a name that is used by the ExportSpan method to emit the span.

func InitializeLogger

func InitializeLogger()

InitializeLogger initializes logging for Knative tests. It should be called prior to executing tests but after command-line flags have been processed. It is recommended doing it in the TestMain() function.

func InitializeMetricExporter

func InitializeMetricExporter(context string)

InitializeMetricExporter initializes the metric exporter logger


type FormatLogger

type FormatLogger func(template string, args ...interface{})

FormatLogger is a printf style function for logging in tests.

type SpewEncoder

type SpewEncoder struct {

SpewEncoder implements zapcore.Encoder interface

func NewSpewEncoder

func NewSpewEncoder(cfg zapcore.EncoderConfig) *SpewEncoder

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 (*SpewEncoder) Clone

func (enc *SpewEncoder) Clone() zapcore.Encoder

Implements zapcore.Encoder interface

func (*SpewEncoder) EncodeEntry

func (enc *SpewEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error)

Implements zapcore.Encoder interface.

type StructuredError

type StructuredError interface {
	GetValues() []interface{}
	WithValues(...interface{}) StructuredError

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 TLogger

type TLogger struct {
	// contains filtered or unexported fields

TLogger is TLogger

func NewTLogger

func NewTLogger(t *testing.T) (*TLogger, func())

Create a TLogger object using the global Zap logger and the current testing.T `defer` a call to second return value immediately after.

func (*TLogger) Cleanup

func (o *TLogger) Cleanup(c func())

Cleanup registers a cleanup callback.

func (*TLogger) Collect

func (o *TLogger) Collect(key string, value interface{})

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 (*TLogger) Error

func (o *TLogger) Error(stringThenKeysAndValues ...interface{})

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 (*TLogger) ErrorIfErr

func (o *TLogger) ErrorIfErr(err error, msg string, keysAndValues ...interface{})

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 (*TLogger) Fatal

func (o *TLogger) Fatal(stringThenKeysAndValues ...interface{})

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 (*TLogger) FatalIfErr

func (o *TLogger) FatalIfErr(err error, msg string, keysAndValues ...interface{})

FatalIfErr is just like ErrorIfErr() but test execution stops immediately

func (*TLogger) Helper

func (o *TLogger) Helper()

Helper cannot work as an indirect call, so just do nothing :( Implements test.T

func (*TLogger) Log

func (o *TLogger) Log(args ...interface{})

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 (*TLogger) Logf

func (o *TLogger) Logf(fmtS string, args ...interface{})

Logf is deprecated: only existing for test.TLegacy compatibility Please use leveled logging via .V().Info() Implements test.TLegacy

func (*TLogger) Name

func (o *TLogger) Name() string

Name is just like testing.T.Name() Implements test.T

func (*TLogger) Parallel

func (o *TLogger) Parallel()

Parallel allows tests or subtests to run in parallel Just calls the testing.T.Parallel() under the hood

func (*TLogger) Run

func (o *TLogger) Run(name string, f func(t *TLogger))

Run a subtest. Just like testing.T.Run but creates a TLogger.

func (*TLogger) SkipNow

func (o *TLogger) SkipNow()

SkipNow immediately stops test execution Implements test.T

func (*TLogger) V

func (o *TLogger) V(level int) logr.InfoLogger

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 (*TLogger) WithName

func (o *TLogger) WithName(name string) *TLogger

WithName() acts like Zap's Named() method. Consistent with logr.Logger.WithName() Appends the name onto the current logger

func (*TLogger) WithValues

func (o *TLogger) WithValues(keysAndValues ...interface{}) *TLogger

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.