uber-go/zap · warning

PANIC=%v

Error message

PANIC=%v

What it means

zapcore/field.go's encodeStringer recovers from panics raised while calling String() on a value passed with zap.Stringer (or similar). If String() panics, the field is encoded with the message "PANIC=%v" containing the recovered panic value rather than letting the panic escape the logging call.

Source

Thrown at zapcore/field.go:227

	for i := range fields {
		fields[i].AddTo(enc)
	}
}

func encodeStringer(key string, stringer interface{}, enc ObjectEncoder) (retErr error) {
	// Try to capture panics (from nil references or otherwise) when calling
	// the String() method, similar to https://golang.org/src/fmt/print.go#L540
	defer func() {
		if err := recover(); err != nil {
			// If it's a nil pointer, just say "<nil>". The likeliest causes are a
			// Stringer 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(stringer); v.Kind() == reflect.Pointer && v.IsNil() {
				enc.AddString(key, "<nil>")
				return
			}

			retErr = fmt.Errorf("PANIC=%v", err)
		}
	}()

	enc.AddString(key, stringer.(fmt.Stringer).String())
	return nil
}

View on GitHub (pinned to bbd4ecbd87)

Solutions

  1. Make the String() method panic-safe: nil-check the receiver and any pointers it dereferences.
  2. Inspect the encoded PANIC=%v value in logs to identify the panicking Stringer and its panic reason.
  3. Stop logging the offending type with zap.Stringer until its String() is fixed (or log safe fields explicitly).

Example fix

// before
func (u *User) String() string { return u.Name } // panics when u is nil
// after
func (u *User) String() string {
    if u == nil { return "<nil>" }
    return u.Name
}
Defensive patterns

Strategy: type-guard

Validate before calling

func safeString(v fmt.Stringer) (s string) {
    defer func() { if recover() != nil { s = "<panic in String()>" } }()
    return v.String()
}
logger.Info("user", zap.String("user", safeString(u)))

Type guard

func stringerSafe(v any) (fmt.Stringer, bool) {
    s, ok := v.(fmt.Stringer)
    if !ok { return nil, false }
    safe := func() (s string) {
        defer func() { if recover() != nil { s = "<nil>" } }()
        return s0(s)
    }
    _ = safe // wrap in your own safe Stringer type
    return safeStringer{s}, true
}

Prevention

When it happens

Trigger: Logging a type implementing fmt.Stringer whose String() method panics — nil pointer receiver dereference, index out of range, or calling methods on zero-value fields — via zap.Stringer(key, v).

Common situations: Stringers that dereference nested pointers which are nil, stringers over structs not yet initialized, mutex-guarded types whose String() is called from a context where state is invalid.

Related errors


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