Zap provides dedicated testing packages to facilitate testing code that uses zap loggers: zaptest for creating test loggers and zaptest/observer for inspecting logged entries.
Import: import "go.uber.org/zap/zaptest"
The zaptest package provides utilities for creating loggers in tests that write to the test output.
func NewLogger(t TestingT, opts ...LoggerOption) *zap.LoggerCreate a logger that writes to the test's output. The logger is configured to:
t.LogfParameters:
t: Test instance (testing.T or testing.B)opts: Optional configuration optionstype TestingT interface {
Logf(format string, args ...interface{})
}Subset of testing.TB implemented by both *testing.T and *testing.B.
type LoggerOption interface {
// unexported method
}
func Level(enab zapcore.LevelEnabler) LoggerOption
func WrapOptions(zapOpts ...zap.Option) LoggerOptionfunc TestSomething(t *testing.T) {
logger := zaptest.NewLogger(t)
defer logger.Sync()
logger.Info("test started")
// Test code that uses logger
result := processWithLogging(logger, input)
logger.Info("test completed", zap.Any("result", result))
assert.Equal(t, expected, result)
}func TestDebugMode(t *testing.T) {
logger := zaptest.NewLogger(t, zaptest.Level(zapcore.DebugLevel))
logger.Debug("this will be logged")
// Test debug-level functionality
}func TestWithStacktraces(t *testing.T) {
logger := zaptest.NewLogger(t,
zaptest.WrapOptions(
zap.AddCaller(),
zap.AddStacktrace(zapcore.WarnLevel),
),
)
logger.Warn("warning with stacktrace")
}type TestingWriter struct {
// unexported fields
}
func NewTestingWriter(t TestingT) *TestingWriterCreate an io.Writer that writes to test output. Implements io.Writer and splits output on newlines.
func (w *TestingWriter) Write(p []byte) (n int, err error)type TimeoutWriter struct {
// unexported fields
}
func NewTimeoutWriter(t TestingT, timeout time.Duration) *TimeoutWriter
func (w *TimeoutWriter) Write(p []byte) (n int, err error)
func (w *TimeoutWriter) Stop() errorWriter that fails the test if no writes occur within the timeout period. Useful for detecting hung tests.
func TestWithTimeout(t *testing.T) {
writer := zaptest.NewTimeoutWriter(t, 1*time.Second)
defer writer.Stop()
logger := zap.New(
zapcore.NewCore(
zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()),
zapcore.AddSync(writer),
zapcore.DebugLevel,
),
)
// Test will fail if no logs written within 1 second
logger.Info("keeping test alive")
}Import: import "go.uber.org/zap/zaptest/observer"
The observer package provides facilities for inspecting logged entries during tests, enabling assertions about log output.
func New(enab zapcore.LevelEnabler) (zapcore.Core, *ObservedLogs)Create a zapcore.Core that captures all log entries and an ObservedLogs instance for inspecting them. The core should be wrapped in a zap.Logger using zap.New().
Parameters:
enab: Level enabler (e.g., zapcore.DebugLevel to capture all levels)Returns:
zapcore.Core: Core to use with zap.New()*ObservedLogs: Observer for inspecting logged entriestype ObservedLogs struct {
// unexported fields
}Collection of logged entries with filtering and inspection methods.
func (o *ObservedLogs) All() []LoggedEntry
func (o *ObservedLogs) TakeAll() []LoggedEntry
func (o *ObservedLogs) AllUntimed() []LoggedEntry
func (o *ObservedLogs) Len() intfunc (o *ObservedLogs) FilterMessage(msg string) *ObservedLogs
func (o *ObservedLogs) FilterMessageSnippet(snippet string) *ObservedLogs
func (o *ObservedLogs) FilterField(field zapcore.Field) *ObservedLogs
func (o *ObservedLogs) FilterFieldKey(key string) *ObservedLogs
func (o *ObservedLogs) FilterLevelExact(lvl zapcore.Level) *ObservedLogs
func (o *ObservedLogs) FilterLoggerName(name string) *ObservedLogs
func (o *ObservedLogs) Filter(f func(LoggedEntry) bool) *ObservedLogsAll filter methods return a new *ObservedLogs containing only entries matching the filter. Original ObservedLogs is unchanged.
type LoggedEntry struct {
Entry zapcore.Entry
Context []zapcore.Field
}Captured log entry with metadata and context fields.
Fields:
func (e LoggedEntry) ContextMap() map[string]interface{}
func (e LoggedEntry) CheckMessageStartsWith(want string) error
func (e LoggedEntry) CheckStackTrace(want string) errorfunc TestLogging(t *testing.T) {
observedCore, logs := observer.New(zapcore.InfoLevel)
logger := zap.New(observedCore)
logger.Info("user logged in", zap.String("username", "alice"))
logger.Warn("slow query", zap.Duration("latency", 500*time.Millisecond))
// Assert log count
assert.Equal(t, 2, logs.Len())
// Inspect entries
entries := logs.All()
assert.Equal(t, "user logged in", entries[0].Message)
assert.Equal(t, zapcore.InfoLevel, entries[0].Level)
// Check context fields
contextMap := entries[0].ContextMap()
assert.Equal(t, "alice", contextMap["username"])
}func TestErrorLogging(t *testing.T) {
core, logs := observer.New(zapcore.DebugLevel)
logger := zap.New(core)
logger.Info("operation started")
logger.Error("operation failed", zap.Error(errors.New("network error")))
logger.Info("cleanup complete")
// Filter to only errors
errorLogs := logs.FilterLevelExact(zapcore.ErrorLevel)
assert.Equal(t, 1, errorLogs.Len())
// Filter by message
failedLogs := logs.FilterMessageSnippet("failed")
assert.Equal(t, 1, failedLogs.Len())
// Filter by field key
logsWithError := logs.FilterFieldKey("error")
assert.Equal(t, 1, logsWithError.Len())
}func TestSlowOperations(t *testing.T) {
core, logs := observer.New(zapcore.DebugLevel)
logger := zap.New(core)
logger.Info("fast op", zap.Duration("duration", 10*time.Millisecond))
logger.Info("slow op", zap.Duration("duration", 600*time.Millisecond))
logger.Info("normal op", zap.Duration("duration", 100*time.Millisecond))
// Custom filter for slow operations (>500ms)
slowLogs := logs.Filter(func(entry observer.LoggedEntry) bool {
contextMap := entry.ContextMap()
if duration, ok := contextMap["duration"].(time.Duration); ok {
return duration > 500*time.Millisecond
}
return false
})
assert.Equal(t, 1, slowLogs.Len())
assert.Equal(t, "slow op", slowLogs.All()[0].Message)
}func TestIncrementalLogging(t *testing.T) {
core, logs := observer.New(zapcore.DebugLevel)
logger := zap.New(core)
// First operation
logger.Info("step 1")
entries := logs.TakeAll() // Take and clear
assert.Equal(t, 1, len(entries))
assert.Equal(t, "step 1", entries[0].Message)
// Second operation
logger.Info("step 2")
entries = logs.TakeAll() // Take and clear
assert.Equal(t, 1, len(entries))
assert.Equal(t, "step 2", entries[0].Message)
// No logs remain
assert.Equal(t, 0, logs.Len())
}func TestStackTraces(t *testing.T) {
core, logs := observer.New(zapcore.DebugLevel)
logger := zap.New(core, zap.AddStacktrace(zapcore.ErrorLevel))
logger.Error("critical error")
entries := logs.FilterLevelExact(zapcore.ErrorLevel).All()
assert.Equal(t, 1, len(entries))
entry := entries[0]
assert.NotEmpty(t, entry.Stack)
// Check stack trace contains expected function
err := entry.CheckStackTrace("TestStackTraces")
assert.NoError(t, err)
}func TestContextLogging(t *testing.T) {
core, logs := observer.New(zapcore.DebugLevel)
logger := zap.New(core)
// Create child logger with context
requestLogger := logger.With(
zap.String("request_id", "req-123"),
zap.String("user", "alice"),
)
requestLogger.Info("processing request")
requestLogger.Info("request complete")
// All entries should have context fields
entries := logs.All()
for _, entry := range entries {
contextMap := entry.ContextMap()
assert.Equal(t, "req-123", contextMap["request_id"])
assert.Equal(t, "alice", contextMap["user"])
}
}func TestSimple(t *testing.T) {
logger := zaptest.NewLogger(t)
// Test code runs, logs appear in test output
}func TestLogAssertions(t *testing.T) {
core, logs := observer.New(zapcore.DebugLevel)
logger := zap.New(core)
// Run code under test
myFunction(logger)
// Assert expected logs
assert.Equal(t, 1, logs.FilterMessage("expected message").Len())
}func TestComprehensive(t *testing.T) {
// Create observed core
observedCore, logs := observer.New(zapcore.DebugLevel)
// Create test writer
testWriter := zaptest.NewTestingWriter(t)
testCore := zapcore.NewCore(
zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()),
zapcore.AddSync(testWriter),
zapcore.DebugLevel,
)
// Tee to both
logger := zap.New(zapcore.NewTee(observedCore, testCore))
// Run test - logs appear in test output AND can be inspected
myFunction(logger)
assert.Equal(t, 1, logs.FilterLevelExact(zapcore.ErrorLevel).Len())
}