-
Notifications
You must be signed in to change notification settings - Fork 0
/
hecate.go
123 lines (106 loc) · 3.99 KB
/
hecate.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
package hecate
// Hecate is the goddess that helped others find their paths.
// This too will help the http call find its way in cases of
// errors.
import (
"encoding/json"
"fmt"
"log"
"net/http"
"runtime"
)
// should probably expand this later to handle a set of errors
// but currently we always return on the first error
type errorInfo struct {
Trace []string `json:"trace"`
Err string `json:"err"`
}
// Ideally, this can be used to pass stuff back up from errors
// down the stack if we need to do something above in a function
// that is using this library
type ErrorBox struct {
Err bool
}
// sometimes you need to dump a stack, and sometimes you don't care. This also handily
// assembles an ErrorBox for you so you don't have to assemble things *and then*
// pass things in.
func ReportAndPassError(str string, responseWriter http.ResponseWriter, status int, stackDump bool) ErrorBox {
errorBox := ErrorBox{true}
HandleError(fmt.Errorf(str), responseWriter, status, stackDump)
return errorBox
}
// this is the equivalent of passing nil down for the
// builtin type error.
func NoErrorOccurred() ErrorBox {
errorBox := ErrorBox{false}
return errorBox
}
var DebugLevel = 0
// This will give you a comprehensive stack, line by line, in json form for easy parsing
func HandleError(err error, responseWriter http.ResponseWriter, status int, stackDump bool) {
handleAllErrors(err, responseWriter, status, stackDump, false)
}
// This will put the entire stack on one line in he json form, so be careful here.
// This operation can be expensive because underlying code stops the world every
// time you use it.
func HandlePanic(responseWriter http.ResponseWriter) {
handleAllErrors(fmt.Errorf("Panic."), responseWriter, http.StatusInternalServerError, true, true)
}
// There are places we might not want stacks, so we do have a bool here
func handleAllErrors(err error, responseWriter http.ResponseWriter, status int, stackDump bool, allStacks bool) {
// we might not want to always output stacks to logs. They are big and slow and sometimes
// we just want to return an error code.
if stackDump == true {
frames := map[int]*string{}
if allStacks {
// one "frame" only. This one is a full
// stack dump including goroutines and
// should only be used in cases of panics,
// not habitual use. Ideally we'd pull
// off each individual frame, but there
// isn't an easy way to do this due to
// needing to stopTheWorld to get this version.
// Yes, I know debug.PrintStack() does this differently
// but it ends up allocating buffers and doing a bunch
// of copies, which I don't like. I'd rather do
// a giant allocation once and then shorten it
// up.
buf := make([]byte, 1<<20)
count := runtime.Stack(buf, true)
temp := string(buf[:count])
frames[0] = &temp
} else {
// take the reporting function off the stack dump
// and return the rest appropriately
fpcs := make([]uintptr, 32) // stack max record in runtime lib is 32
length := runtime.Callers(2, fpcs)
for i := 0; i < length; i++ {
f := runtime.FuncForPC(fpcs[i])
file, line := f.FileLine(fpcs[i])
temp := fmt.Sprintf("%d %s:%d %s\n", i, file, line, f.Name())
log.Printf("%s", temp)
frames[i] = &temp
}
}
// this just tells us if we are outputting the full stack and error to our fancy-pants json call
// or if we want to fall through (below) to merely returning the error
if DebugLevel > 0 {
error := errorInfo{nil, err.Error()}
for _, frame := range frames {
error.Trace = append(error.Trace, *frame)
}
jsonBytes, err := json.Marshal(error)
// this isn't ideal, but we can at least return the original error without the stack dump
if err != nil {
http.Error(responseWriter, err.Error(), status)
return
}
// all the dumping of the stack
responseWriter.Header().Set("Content-Type", "application/json")
responseWriter.WriteHeader(status)
responseWriter.Write(jsonBytes)
return
}
}
http.Error(responseWriter, err.Error(), status)
}