uber-go/zap · warning
PANIC=%v
Error message
PANIC=%v
What it means
zapcore/error.go's encodeError recovers from panics raised while calling err.Error() (or MarshalLogObject on the error) during field encoding. Instead of crashing the logger, it encodes the string "PANIC=%v" with the recovered value as the field's value.
Source
Thrown at zapcore/error.go:60
// "errorVerbose": fmt.Sprintf("%+v", err),
// "errorCauses": [
// ...
// ],
// }
func encodeError(key string, err error, enc ObjectEncoder) (retErr error) {
// Try to capture panics (from nil references or otherwise) when calling
// the Error() method
defer func() {
if rerr := recover(); rerr != nil {
// If it's a nil pointer, just say "<nil>". The likeliest causes are a
// error that fails to guard against nil or a nil pointer for a
// value receiver, and in either case, "<nil>" is a nice result.
if v := reflect.ValueOf(err); v.Kind() == reflect.Pointer && v.IsNil() {
enc.AddString(key, "<nil>")
return
}
retErr = fmt.Errorf("PANIC=%v", rerr)
}
}()
basic := err.Error()
enc.AddString(key, basic)
switch e := err.(type) {
case errorGroup:
return enc.AddArray(key+"Causes", errArray(e.Errors()))
case fmt.Formatter:
verbose := fmt.Sprintf("%+v", e)
if verbose != basic {
// This is a rich error type, like those produced by
// github.com/pkg/errors.
enc.AddString(key+"Verbose", verbose)
}
}
return nilView on GitHub (pinned to bbd4ecbd87)
Solutions
- Fix the custom error type so Error() cannot panic (nil-check wrapped errors and internal fields).
- Audit logged values: ensure zap.Error is not given an uninitialized/zero-value error struct whose Error() dereferences nil.
- Check the log output for the PANIC=%v message — the %v content identifies the panic cause (e.g. runtime error: invalid memory address).
Example fix
// before
func (e *MyErr) Error() string { return e.msg } // panics if e is nil
// after
func (e *MyErr) Error() string {
if e == nil { return "<nil>" }
return e.msg
} Defensive patterns
Strategy: type-guard
Validate before calling
func safeLogError(logger *zap.Logger, err error) {
if err == nil { return }
func() {
defer func() { _ = recover() }()
_ = err.Error()
}() // reaches here safely only if Error() doesn't panic
logger.Error("op failed", zap.Error(err))
} Type guard
func errSafeToLog(err error) (safe error, ok bool) {
ok = func() (ok bool) {
defer func() { if recover() != nil { ok = false } }()
_ = err.Error()
return true
}()
if ok { return err, true }
return errors.New("<unloggable error>"), false
} Prevention
- Make custom error types' Error() methods nil-safe and free of side effects.
- Never implement Error() with lazy dereferences of unset wrapped errors.
- Scan logs periodically for "PANIC=" markers to find buggy error types.
When it happens
Trigger: Logging an error whose Error() method panics — e.g. nil pointer dereference inside a custom error's Error(), or a panic thrown by the error's MarshalLogObject implementation, passed via zap.Error(err) or zap.NamedError.
Common situations: Custom error types with methods dereferencing nil state, lazy errors wrapping values that became nil, third-party error types with buggy Error() implementations being logged.
Related errors
- PANIC=%v
- can't unmarshal a nil *Level
- invalid increase level, as level %q is allowed by increased
- ce.Message
AI-assisted analysis of uber-go/zap@bbd4ecbd87 (2026-08-31).
Data as JSON: /api/errors/59813289515d2823.
Report an issue: GitHub.