pkg/errors

Proposal: adding custom fields to errors

masterada opened this issue · 3 comments

I suggest a WithFields() method that lets you add arbitrary data to your error object, that can later be fetched (even if it was added to a nested error). We use this with alternating key-values for adding useful information to our errors for logging them later.

Usage:

    err := errors.New("My error")
    err = errors.WithFields(err, MyCustomKeyValue{ key: "statusCode", value: 200})
    err = errors.WithFields(err, "any data", "can be added", 123)
    err = errors.WithStack(err)
    fields := errors.Fields(err)

Implementation:

type withFields struct {
	cause  error
	fields []interface{}
}

func WithFields(err error, fields ...interface{}) error {
	if len(fields) == 0 {
		return err
	}

	return &withFields{
		cause:  err,
		fields: fields,
	}
}

func (e *withFields) Error() string {
	return e.cause.Error()
}

func (e *withFields) Fields() []interface{} {
	return e.fields
}

func (e *withFields) Cause() error {
	return e.cause
}

func (e *withFields) Format(s fmt.State, verb rune) {
	switch verb {
	case 'v':
		if s.Flag('+') {
			fmt.Fprintf(s, "%+v\n", e.Cause())
			return
		}
		fallthrough
	case 's', 'q':
		io.WriteString(s, e.Error())
	}
}

func Fields(err error) []interface{} {
	type causer interface {
		Cause() error
	}

	type fielder interface {
		Fields() []interface{}
	}

	var fields []interface{}
	for err != nil {
		if fErr, ok := err.(fielder); ok {
			fields = append(fields, fErr.Fields()...)
		}

		cause, ok := err.(causer)
		if !ok {
			break
		}
		err = cause.Cause()
	}

	return fields
}

Duplicate of #70

Duplicate of #34

OK, sorry for duplicating, didn't check closed issues.