You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

265 lines
7.1 KiB

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))
}