-
Notifications
You must be signed in to change notification settings - Fork 2
/
error.go
198 lines (166 loc) · 5.01 KB
/
error.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
// Copyright (c) 2012-2013 Matt Nunogawa @amattn
// This source code is release under the MIT License, http://opensource.org/licenses/MIT
package deeperror
import (
"fmt"
"log"
"net/http"
"runtime"
"strconv"
"strings"
)
var globalErrorLoggingEnabled bool
func init() {
globalErrorLoggingEnabled = false
}
const (
globalDefaultStatusCode = http.StatusInternalServerError
)
//
type DeepError struct {
Num int64
Filename string
CallingMethod string
Line int
EndUserMsg string
DebugMsg string
DebugFields map[string]interface{}
Err error // inner or source error
StatusCode int
StackTrace string
}
// Primary Constructor. Create a DeepError ptr with the given number, end user message and optional parent error.
func New(num int64, endUserMsg string, parentErr error) *DeepError {
e := new(DeepError)
e.Num = num
e.EndUserMsg = endUserMsg
e.Err = parentErr
e.StatusCode = globalDefaultStatusCode
e.DebugFields = make(map[string]interface{})
gerr, ok := parentErr.(*DeepError)
if ok {
if gerr != nil {
e.StatusCode = gerr.StatusCode
}
}
pc, file, line, ok := runtime.Caller(1)
if ok {
e.Line = line
components := strings.Split(file, "/")
e.Filename = components[(len(components) - 1)]
f := runtime.FuncForPC(pc)
e.CallingMethod = f.Name()
}
const size = 1 << 12
buf := make([]byte, size)
n := runtime.Stack(buf, false)
e.StackTrace = string(buf[:n])
if globalErrorLoggingEnabled {
log.Print(e)
}
return e
}
// HTTP variant. Create a DeepError with the given http status code
func NewHTTPError(num int64, endUserMsg string, err error, statusCode int) *DeepError {
derr := New(num, endUserMsg, err)
derr.StatusCode = statusCode
if len(endUserMsg) == 0 {
derr.EndUserMsg = http.StatusText(statusCode)
}
return derr
}
// Convenience method. creates a simple DeepError with the given error number. The error message is set to "TODO"
func NewTODOError(num int64, printArgs ...interface{}) *DeepError {
derr := New(num, "TODO", nil)
for i, printArg := range printArgs {
derr.AddDebugField(strconv.Itoa(i), printArg)
}
return derr
}
// Convenience method. This will return nil if parrentErr == nil. Otherwise it will create a DeepError and return that.
func NewOrNilFromParent(num int64, endUserMsg string, parentErr error) error {
if parentErr == nil {
return nil
}
return New(num, endUserMsg, parentErr)
}
// Convenience method. Equivalient to derr:=New(...); log.Fatal(derr)
func Fatal(num int64, endUserMsg string, parentErr error) {
derr := New(num, endUserMsg, parentErr)
log.Fatal(derr)
}
// Add arbitrary debugging data to a given DeepError
func (derr *DeepError) AddDebugField(key string, value interface{}) {
derr.DebugFields[key] = value
}
// cConform to the new Unwrap interface.
// Unwrap() will expose errors further down the error chain
// This should allow support for Is() and As() in Go 1.13 and later
// Alternatively, earlier version of Go can
// import "golang.org/x/xerrors" to get library support
// for Is(), As(), and Unwrap()
// see https://blog.golang.org/go1.13-errors for details
func (derr *DeepError) Unwrap() error {
return derr.Err
}
// internal usage for formatting/pretty printing
func prependToLines(para, prefix string) string {
lines := strings.Split(para, "\n")
for i, line := range lines {
lines[i] = prefix + line
}
return strings.Join(lines, "\n")
}
// Check if the current status code matches the global default
func (derr *DeepError) StatusCodeIsDefaultValue() bool {
if derr.StatusCode == globalDefaultStatusCode {
return true
} else {
return false
}
}
// Conform to the go built-in error interface
// http://golang.org/pkg/builtin/#error
func (derr *DeepError) Error() string {
parentError := "nil"
if derr.Err != nil {
parentError = prependToLines(derr.Err.Error(), "-- ")
}
debugFieldStrings := make([]string, 0, len(derr.DebugFields))
for k, v := range derr.DebugFields {
str := fmt.Sprintf("\n-- DebugField[%s]: %+v", k, v)
debugFieldStrings = append(debugFieldStrings, str)
}
dbgMsg := ""
if len(derr.DebugMsg) > 0 {
dbgMsg = "\n-- DebugMsg: " + derr.DebugMsg
}
return fmt.Sprintln(
"\n\n-- DeepError",
derr.Num,
derr.StatusCode,
derr.Filename,
derr.CallingMethod,
"line:", derr.Line,
"\n-- EndUserMsg: ", derr.EndUserMsg,
dbgMsg,
strings.Join(debugFieldStrings, ""),
"\n-- StackTrace:",
strings.TrimLeft(prependToLines(derr.StackTrace, "-- "), " "),
"\n-- ParentError:", parentError,
)
}
// enable/disable automatic logging of deeperrors upon creation
func ErrorLoggingEnabled() bool {
return globalErrorLoggingEnabled
}
// anything performed in this anonymous function will not trigger automatic logging of deeperrors upon creation
type NoErrorsLoggingAction func()
// you can use this method to temporarily disable automatic logging of deeperrors
func ExecWithoutErrorLogging(action NoErrorsLoggingAction) {
// this is racy... I feel ashamed.
original := globalErrorLoggingEnabled
globalErrorLoggingEnabled = false
action()
globalErrorLoggingEnabled = original
}