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 nil

View on GitHub (pinned to bbd4ecbd87)

Solutions

  1. Fix the custom error type so Error() cannot panic (nil-check wrapped errors and internal fields).
  2. Audit logged values: ensure zap.Error is not given an uninitialized/zero-value error struct whose Error() dereferences nil.
  3. 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

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


AI-assisted analysis of uber-go/zap@bbd4ecbd87 (2026-08-31). Data as JSON: /api/errors/59813289515d2823. Report an issue: GitHub.