// Copyright (C) 2014 Space Monkey, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package errors import ( "fmt" "log" "runtime" "strings" ) var ( // Change this method if you want errors to log somehow else LogMethod = log.Printf ErrorGroupError = NewClass("Error Group Error") ) // LogWithStack will log the given messages with the current stack func LogWithStack(messages ...interface{}) { buf := make([]byte, Config.Stacklogsize) buf = buf[:runtime.Stack(buf, false)] LogMethod("%s\n%s", fmt.Sprintln(messages...), buf) } // CatchPanic can be used to catch panics and turn them into errors. See the // example. func CatchPanic(err_ref *error) { r := recover() if r == nil { return } err, ok := r.(error) if ok { *err_ref = PanicError.Wrap(err) return } *err_ref = PanicError.New("%v", r) } // ErrorGroup is a type for collecting errors from a bunch of independent // tasks. ErrorGroups are not threadsafe. See the example for usage. type ErrorGroup struct { Errors []error limit int excess int } // NewErrorGroup makes a new ErrorGroup func NewErrorGroup() *ErrorGroup { return &ErrorGroup{} } // NewBoundedErrorGroup makes a new ErrorGroup that will not track more than // limit errors. Once the limit is reached, the ErrorGroup will track // additional errors as excess. func NewBoundedErrorGroup(limit int) *ErrorGroup { return &ErrorGroup{ limit: limit, } } // Add is called with errors. nil errors are ignored. func (e *ErrorGroup) Add(err error) { if err == nil { return } if e.limit > 0 && len(e.Errors) == e.limit { e.excess++ } else { e.Errors = append(e.Errors, err) } } // Finalize will collate all the found errors. If no errors were found, it will // return nil. If one error was found, it will be returned directly. Otherwise // an ErrorGroupError will be returned. func (e *ErrorGroup) Finalize() error { if len(e.Errors) == 0 { return nil } if len(e.Errors) == 1 && e.excess == 0 { return e.Errors[0] } msgs := make([]string, 0, len(e.Errors)) for _, err := range e.Errors { msgs = append(msgs, err.Error()) } if e.excess > 0 { msgs = append(msgs, fmt.Sprintf("... and %d more.", e.excess)) e.excess = 0 } e.Errors = nil return ErrorGroupError.New(strings.Join(msgs, "\n")) } // LoggingErrorGroup is similar to ErrorGroup except that instead of collecting // all of the errors, it logs the errors immediately and just counts how many // non-nil errors have been seen. See the ErrorGroup example for usage. type LoggingErrorGroup struct { name string total int failed int } // NewLoggingErrorGroup returns a new LoggingErrorGroup with the given name. func NewLoggingErrorGroup(name string) *LoggingErrorGroup { return &LoggingErrorGroup{name: name} } // Add will handle a given error. If the error is non-nil, total and failed // are both incremented and the error is logged. If the error is nil, only // total is incremented. func (e *LoggingErrorGroup) Add(err error) { e.total++ if err != nil { LogMethod("%s: %s", e.name, err) e.failed++ } } // Finalize returns no error if no failures were observed, otherwise it will // return an ErrorGroupError with statistics about the observed errors. func (e *LoggingErrorGroup) Finalize() (err error) { if e.failed > 0 { err = ErrorGroupError.New("%s: %d of %d failed.", e.name, e.failed, e.total) } e.total = 0 e.failed = 0 return err } type Finalizer interface { Finalize() error } // Finalize takes a group of ErrorGroups and joins them together into one error func Finalize(finalizers ...Finalizer) error { var errs ErrorGroup for _, finalizer := range finalizers { errs.Add(finalizer.Finalize()) } return errs.Finalize() }