This change removes a code pattern that I noticed while on a late night audit of cosmovisor in which strings.Builder.WriteString(fmt.Sprintf(...)) calls were being made, yet that's counterproductive to using fmt.Fprintf which will check whether the writer implements .WriteString and then avoids the need to firstly build a string using fmt.Sprintf. The performance wins from this change transcend all dimensions as exhibited below: ```shell $ benchstat before.txt after.txt name old time/op new time/op delta DetailString-8 5.48µs ±23% 4.40µs ±11% -19.79% (p=0.000 n=20+17) name old alloc/op new alloc/op delta DetailString-8 2.63kB ± 0% 2.11kB ± 0% -19.76% (p=0.000 n=20+20) name old allocs/op new allocs/op delta DetailString-8 63.0 ± 0% 50.0 ± 0% -20.63% (p=0.000 n=20+20) ``` Fixes #13229
86 lines
2.0 KiB
Go
86 lines
2.0 KiB
Go
package errors
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/rs/zerolog"
|
|
)
|
|
|
|
// MultiError is an error combining multiple other errors.
|
|
// It will never have 0 or 1 errors. It will always have two or more.
|
|
type MultiError struct {
|
|
errs []error
|
|
}
|
|
|
|
// FlattenErrors possibly creates a MultiError.
|
|
// Nil entries are ignored.
|
|
// If all provided errors are nil (or nothing is provided), nil is returned.
|
|
// If only one non-nil error is provided, it is returned unchanged.
|
|
// If two or more non-nil errors are provided, the returned error will be of type *MultiError
|
|
// and it will contain each non-nil error.
|
|
func FlattenErrors(errs ...error) error {
|
|
rv := MultiError{}
|
|
for _, err := range errs {
|
|
if err != nil {
|
|
if merr, isMerr := err.(*MultiError); isMerr {
|
|
rv.errs = append(rv.errs, merr.errs...)
|
|
} else {
|
|
rv.errs = append(rv.errs, err)
|
|
}
|
|
}
|
|
}
|
|
switch rv.Len() {
|
|
case 0:
|
|
return nil
|
|
case 1:
|
|
return rv.errs[0]
|
|
}
|
|
return &rv
|
|
}
|
|
|
|
// GetErrors gets all the errors that make up this MultiError.
|
|
func (e MultiError) GetErrors() []error {
|
|
// Return a copy of the errs slice to prevent alteration of the original slice.
|
|
rv := make([]error, e.Len())
|
|
copy(rv, e.errs)
|
|
return rv
|
|
}
|
|
|
|
// Len gets the number of errors in this MultiError.
|
|
func (e MultiError) Len() int {
|
|
return len(e.errs)
|
|
}
|
|
|
|
// Error implements the error interface for a MultiError.
|
|
func (e *MultiError) Error() string {
|
|
var sb strings.Builder
|
|
fmt.Fprintf(&sb, "%d errors: ", len(e.errs))
|
|
for i, err := range e.errs {
|
|
if i != 0 {
|
|
sb.WriteString(", ")
|
|
}
|
|
fmt.Fprintf(&sb, "%d: %v", i+1, err)
|
|
}
|
|
return sb.String()
|
|
}
|
|
|
|
// String implements the string interface for a MultiError.
|
|
func (e MultiError) String() string {
|
|
return e.Error()
|
|
}
|
|
|
|
func LogErrors(logger *zerolog.Logger, msg string, err error) {
|
|
switch err := err.(type) {
|
|
case *MultiError:
|
|
if msg != "" {
|
|
logger.Error().Msg(msg)
|
|
}
|
|
for i, e := range err.GetErrors() {
|
|
logger.Error().Err(e).Msg(fmt.Sprintf(" %d:", i+1))
|
|
}
|
|
default:
|
|
logger.Error().Err(err).Msg(msg)
|
|
}
|
|
}
|