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.

214 lines
4.9 KiB

9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
7 years ago
  1. package common
  2. import (
  3. "fmt"
  4. "runtime"
  5. )
  6. //----------------------------------------
  7. // Convenience method.
  8. func ErrorWrap(cause interface{}, format string, args ...interface{}) Error {
  9. if causeCmnError, ok := cause.(*cmnError); ok {
  10. msg := fmt.Sprintf(format, args...)
  11. return causeCmnError.Stacktrace().Trace(1, msg)
  12. } else if cause == nil {
  13. return newCmnError(FmtError{format, args}).Stacktrace()
  14. } else {
  15. // NOTE: causeCmnError is a typed nil here.
  16. msg := fmt.Sprintf(format, args...)
  17. return newCmnError(cause).Stacktrace().Trace(1, msg)
  18. }
  19. }
  20. //----------------------------------------
  21. // Error & cmnError
  22. /*
  23. Usage with arbitrary error data:
  24. ```go
  25. // Error construction
  26. type MyError struct{}
  27. var err1 error = NewErrorWithData(MyError{}, "my message")
  28. ...
  29. // Wrapping
  30. var err2 error = ErrorWrap(err1, "another message")
  31. if (err1 != err2) { panic("should be the same")
  32. ...
  33. // Error handling
  34. switch err2.Data().(type){
  35. case MyError: ...
  36. default: ...
  37. }
  38. ```
  39. */
  40. type Error interface {
  41. Error() string
  42. Stacktrace() Error
  43. Trace(offset int, format string, args ...interface{}) Error
  44. Data() interface{}
  45. }
  46. // New Error with formatted message.
  47. // The Error's Data will be a FmtError type.
  48. func NewError(format string, args ...interface{}) Error {
  49. err := FmtError{format, args}
  50. return newCmnError(err)
  51. }
  52. // New Error with specified data.
  53. func NewErrorWithData(data interface{}) Error {
  54. return newCmnError(data)
  55. }
  56. type cmnError struct {
  57. data interface{} // associated data
  58. msgtraces []msgtraceItem // all messages traced
  59. stacktrace []uintptr // first stack trace
  60. }
  61. var _ Error = &cmnError{}
  62. // NOTE: do not expose.
  63. func newCmnError(data interface{}) *cmnError {
  64. return &cmnError{
  65. data: data,
  66. msgtraces: nil,
  67. stacktrace: nil,
  68. }
  69. }
  70. // Implements error.
  71. func (err *cmnError) Error() string {
  72. return fmt.Sprintf("%v", err)
  73. }
  74. // Captures a stacktrace if one was not already captured.
  75. func (err *cmnError) Stacktrace() Error {
  76. if err.stacktrace == nil {
  77. var offset = 3
  78. var depth = 32
  79. err.stacktrace = captureStacktrace(offset, depth)
  80. }
  81. return err
  82. }
  83. // Add tracing information with msg.
  84. // Set n=0 unless wrapped with some function, then n > 0.
  85. func (err *cmnError) Trace(offset int, format string, args ...interface{}) Error {
  86. msg := fmt.Sprintf(format, args...)
  87. return err.doTrace(msg, offset)
  88. }
  89. // Return the "data" of this error.
  90. // Data could be used for error handling/switching,
  91. // or for holding general error/debug information.
  92. func (err *cmnError) Data() interface{} {
  93. return err.data
  94. }
  95. func (err *cmnError) doTrace(msg string, n int) Error {
  96. pc, _, _, _ := runtime.Caller(n + 2) // +1 for doTrace(). +1 for the caller.
  97. // Include file & line number & msg.
  98. // Do not include the whole stack trace.
  99. err.msgtraces = append(err.msgtraces, msgtraceItem{
  100. pc: pc,
  101. msg: msg,
  102. })
  103. return err
  104. }
  105. func (err *cmnError) Format(s fmt.State, verb rune) {
  106. switch verb {
  107. case 'p':
  108. s.Write([]byte(fmt.Sprintf("%p", &err)))
  109. default:
  110. if s.Flag('#') {
  111. s.Write([]byte("--= Error =--\n"))
  112. // Write data.
  113. s.Write([]byte(fmt.Sprintf("Data: %#v\n", err.data)))
  114. // Write msg trace items.
  115. s.Write([]byte(fmt.Sprintf("Msg Traces:\n")))
  116. for i, msgtrace := range err.msgtraces {
  117. s.Write([]byte(fmt.Sprintf(" %4d %s\n", i, msgtrace.String())))
  118. }
  119. // Write stack trace.
  120. if err.stacktrace != nil {
  121. s.Write([]byte(fmt.Sprintf("Stack Trace:\n")))
  122. for i, pc := range err.stacktrace {
  123. fnc := runtime.FuncForPC(pc)
  124. file, line := fnc.FileLine(pc)
  125. s.Write([]byte(fmt.Sprintf(" %4d %s:%d\n", i, file, line)))
  126. }
  127. }
  128. s.Write([]byte("--= /Error =--\n"))
  129. } else {
  130. // Write msg.
  131. s.Write([]byte(fmt.Sprintf("%v", err.data)))
  132. }
  133. }
  134. }
  135. //----------------------------------------
  136. // stacktrace & msgtraceItem
  137. func captureStacktrace(offset int, depth int) []uintptr {
  138. var pcs = make([]uintptr, depth)
  139. n := runtime.Callers(offset, pcs)
  140. return pcs[0:n]
  141. }
  142. type msgtraceItem struct {
  143. pc uintptr
  144. msg string
  145. }
  146. func (mti msgtraceItem) String() string {
  147. fnc := runtime.FuncForPC(mti.pc)
  148. file, line := fnc.FileLine(mti.pc)
  149. return fmt.Sprintf("%s:%d - %s",
  150. file, line,
  151. mti.msg,
  152. )
  153. }
  154. //----------------------------------------
  155. // fmt error
  156. /*
  157. FmtError is the data type for NewError() (e.g. NewError().Data().(FmtError))
  158. Theoretically it could be used to switch on the format string.
  159. ```go
  160. // Error construction
  161. var err1 error = NewError("invalid username %v", "BOB")
  162. var err2 error = NewError("another kind of error")
  163. ...
  164. // Error handling
  165. switch err1.Data().(cmn.FmtError).Format() {
  166. case "invalid username %v": ...
  167. case "another kind of error": ...
  168. default: ...
  169. }
  170. ```
  171. */
  172. type FmtError struct {
  173. format string
  174. args []interface{}
  175. }
  176. func (fe FmtError) Error() string {
  177. return fmt.Sprintf(fe.format, fe.args...)
  178. }
  179. func (fe FmtError) String() string {
  180. return fmt.Sprintf("FmtError{format:%v,args:%v}",
  181. fe.format, fe.args)
  182. }
  183. func (fe FmtError) Format() string {
  184. return fe.format
  185. }