package common import ( "fmt" "runtime" ) //---------------------------------------- // Convenience methods // ErrorWrap will just call .TraceFrom(), or create a new *cmnError. func ErrorWrap(cause interface{}, format string, args ...interface{}) Error { msg := Fmt(format, args...) if causeCmnError, ok := cause.(*cmnError); ok { return causeCmnError.TraceFrom(1, msg) } // NOTE: cause may be nil. // NOTE: do not use causeCmnError here, not the same as nil. return newError(msg, cause, cause).Stacktrace() } //---------------------------------------- // Error & cmnError /* Usage: ```go // Error construction var someT = errors.New("Some err type") var err1 error = NewErrorWithT(someT, "my message") ... // Wrapping var err2 error = ErrorWrap(err1, "another message") if (err1 != err2) { panic("should be the same") ... // Error handling switch err2.T() { case someT: ... default: ... } ``` */ type Error interface { Error() string Message() string Stacktrace() Error Trace(format string, args ...interface{}) Error TraceFrom(offset int, format string, args ...interface{}) Error Cause() interface{} WithT(t interface{}) Error T() interface{} Format(s fmt.State, verb rune) } // New Error with no cause where the type is the format string of the message.. func NewError(format string, args ...interface{}) Error { msg := Fmt(format, args...) return newError(msg, nil, format) } // New Error with specified type and message. func NewErrorWithT(t interface{}, format string, args ...interface{}) Error { msg := Fmt(format, args...) return newError(msg, nil, t) } // NOTE: The name of a function "NewErrorWithCause()" implies that you are // creating a new Error, yet, if the cause is an Error, creating a new Error to // hold a ref to the old Error is probably *not* what you want to do. // So, use ErrorWrap(cause, format, a...) instead, which returns the same error // if cause is an Error. // IF you must set an Error as the cause of an Error, // then you can use the WithCauser interface to do so manually. // e.g. (error).(tmlibs.WithCauser).WithCause(causeError) type WithCauser interface { WithCause(cause interface{}) Error } type cmnError struct { msg string // first msg which also appears in msg cause interface{} // underlying cause (or panic object) t interface{} // for switching on error msgtraces []msgtraceItem // all messages traced stacktrace []uintptr // first stack trace } var _ WithCauser = &cmnError{} var _ Error = &cmnError{} // NOTE: do not expose. func newError(msg string, cause interface{}, t interface{}) *cmnError { return &cmnError{ msg: msg, cause: cause, t: t, msgtraces: nil, stacktrace: nil, } } func (err *cmnError) Message() string { return err.msg } func (err *cmnError) Error() string { return fmt.Sprintf("%v", err) } // Captures a stacktrace if one was not already captured. func (err *cmnError) Stacktrace() Error { if err.stacktrace == nil { var offset = 3 var depth = 32 err.stacktrace = captureStacktrace(offset, depth) } return err } // Add tracing information with msg. func (err *cmnError) Trace(format string, args ...interface{}) Error { msg := Fmt(format, args...) return err.doTrace(msg, 0) } // Same as Trace, but traces the line `offset` calls out. // If n == 0, the behavior is identical to Trace(). func (err *cmnError) TraceFrom(offset int, format string, args ...interface{}) Error { msg := Fmt(format, args...) return err.doTrace(msg, offset) } // Return last known cause. // NOTE: The meaning of "cause" is left for the caller to define. // There exists no "canonical" definition of "cause". // Instead of blaming, try to handle it, or organize it. func (err *cmnError) Cause() interface{} { return err.cause } // Overwrites the Error's cause. func (err *cmnError) WithCause(cause interface{}) Error { err.cause = cause return err } // Overwrites the Error's type. func (err *cmnError) WithT(t interface{}) Error { err.t = t return err } // Return the "type" of this message, primarily for switching // to handle this Error. func (err *cmnError) T() interface{} { return err.t } func (err *cmnError) doTrace(msg string, n int) Error { pc, _, _, _ := runtime.Caller(n + 2) // +1 for doTrace(). +1 for the caller. // Include file & line number & msg. // Do not include the whole stack trace. err.msgtraces = append(err.msgtraces, msgtraceItem{ pc: pc, msg: msg, }) return err } func (err *cmnError) Format(s fmt.State, verb rune) { switch verb { case 'p': s.Write([]byte(fmt.Sprintf("%p", &err))) default: if s.Flag('#') { s.Write([]byte("--= Error =--\n")) // Write msg. s.Write([]byte(fmt.Sprintf("Message: %#s\n", err.msg))) // Write cause. s.Write([]byte(fmt.Sprintf("Cause: %#v\n", err.cause))) // Write type. s.Write([]byte(fmt.Sprintf("T: %#v\n", err.t))) // Write msg trace items. s.Write([]byte(fmt.Sprintf("Msg Traces:\n"))) for i, msgtrace := range err.msgtraces { s.Write([]byte(fmt.Sprintf(" %4d %s\n", i, msgtrace.String()))) } // Write stack trace. if err.stacktrace != nil { s.Write([]byte(fmt.Sprintf("Stack Trace:\n"))) for i, pc := range err.stacktrace { fnc := runtime.FuncForPC(pc) file, line := fnc.FileLine(pc) s.Write([]byte(fmt.Sprintf(" %4d %s:%d\n", i, file, line))) } } s.Write([]byte("--= /Error =--\n")) } else { // Write msg. if err.cause != nil { s.Write([]byte(fmt.Sprintf("Error{`%s` (cause: %v)}", err.msg, err.cause))) // TODO tick-esc? } else { s.Write([]byte(fmt.Sprintf("Error{`%s`}", err.msg))) // TODO tick-esc? } } } } //---------------------------------------- // stacktrace & msgtraceItem func captureStacktrace(offset int, depth int) []uintptr { var pcs = make([]uintptr, depth) n := runtime.Callers(offset, pcs) return pcs[0:n] } type msgtraceItem struct { pc uintptr msg string } func (mti msgtraceItem) String() string { fnc := runtime.FuncForPC(mti.pc) file, line := fnc.FileLine(mti.pc) return fmt.Sprintf("%s:%d - %s", file, line, mti.msg, ) } //---------------------------------------- // Panic wrappers // XXX DEPRECATED // A panic resulting from a sanity check means there is a programmer error // and some guarantee is not satisfied. // XXX DEPRECATED func PanicSanity(v interface{}) { panic(Fmt("Panicked on a Sanity Check: %v", v)) } // A panic here means something has gone horribly wrong, in the form of data corruption or // failure of the operating system. In a correct/healthy system, these should never fire. // If they do, it's indicative of a much more serious problem. // XXX DEPRECATED func PanicCrisis(v interface{}) { panic(Fmt("Panicked on a Crisis: %v", v)) } // Indicates a failure of consensus. Someone was malicious or something has // gone horribly wrong. These should really boot us into an "emergency-recover" mode // XXX DEPRECATED func PanicConsensus(v interface{}) { panic(Fmt("Panicked on a Consensus Failure: %v", v)) } // For those times when we're not sure if we should panic // XXX DEPRECATED func PanicQ(v interface{}) { panic(Fmt("Panicked questionably: %v", v)) }