forked from azer/logger
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpanic.go
More file actions
150 lines (135 loc) · 3.12 KB
/
Copy pathpanic.go
File metadata and controls
150 lines (135 loc) · 3.12 KB
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
package logger
import (
"bytes"
"fmt"
"github.com/maruel/panicparse/stack"
"log"
"os"
goruntime "runtime"
"strings"
)
const (
maxPanicLines = 300
)
type PanicError struct {
Reason string
Stack string
GoroutineBuckets []*stack.Bucket
}
func (pe PanicError) Error() string {
return pe.Pretty()
}
func (pe PanicError) Pretty() string {
return fmt.Sprintf("%s\n\n%s", pe.Reason, pe.Stack)
}
// WrapRecover wraps panic with prettified stack.
// You must provide result of recover() as an argument. If nothing is recovered, it returns nil.
//
// Example:
// defer func() {
// if err := logger.WrapRecover(recover()); err != nil {
// logger.Fatal("Failed with {}", err.Pretty())
// }
// }()
func WrapRecover(r interface{}) *PanicError {
if r == nil {
return nil
}
reason := fmt.Sprintf("panic: %s", r)
st := make([]byte, 1024)
for {
n := goruntime.Stack(st, false)
if n < len(st) {
st = st[:n]
break
}
st = make([]byte, 2*len(st))
}
c, err := stack.ParseDump(bytes.NewReader(st), os.Stdout, true)
if err != nil {
log.Printf("warning: unable to parse panic stacktrace: %v\n", err)
return &PanicError{
Reason: reason,
Stack: string(st),
}
}
// Find out similar goroutine traces and group them into buckets.
buckets := stack.Aggregate(c.Goroutines, stack.AnyValue)
// Calculate alignment.
srcLen := 0
for _, bucket := range buckets {
for _, line := range bucket.Signature.Stack.Calls {
if l := len(line.SrcLine()); l > srcLen {
srcLen = l
}
}
}
prettyStack := ""
var totalLines int
for i, bucket := range buckets {
panicIndex := -1
for i, call := range bucket.Stack.Calls {
if call.Func.Name() == "panic" {
panicIndex = i
}
}
if i == 0 {
// remove stacks before main panic
bucket.Stack.Calls = bucket.Stack.Calls[panicIndex+1:]
} else if i != 0 {
// dim text color
prettyStack += "\n\x1b[2m"
}
// Print the goroutine header.
extra := ""
if s := bucket.SleepString(); s != "" {
extra += "[" + s + "]"
}
if bucket.Locked {
extra += "[locked]"
}
if c := bucket.CreatedByString(false); c != "" {
extra += "[created by " + c + "]"
}
goroutineIDs := ""
if len(bucket.IDs) < 3 {
var ids []string
for _, id := range bucket.IDs {
ids = append(ids, fmt.Sprintf("#%d", id))
}
goroutineIDs = strings.Join(ids, ", ")
} else {
goroutineIDs = fmt.Sprintf("Group of %d goroutines", len(bucket.IDs))
}
prettyStack += fmt.Sprintf("%s: %s %s", goroutineIDs, bucket.State, extra)
if totalLines >= maxPanicLines {
prettyStack += " (...)\n"
continue
} else {
prettyStack += "\n"
totalLines += 1
}
// Print the stack lines.
for _, line := range bucket.Stack.Calls {
prettyStack += fmt.Sprintf(
" %-*s %s(%s)\n",
srcLen, line.SrcLine(),
line.Func.PkgDotName(), &line.Args)
totalLines += 1
}
if bucket.Stack.Elided {
prettyStack += " (...)\n"
totalLines += 1
}
if i != 0 {
// reset text color
prettyStack += "\x1b[0m"
totalLines += 1
}
}
return &PanicError{
Reason: reason,
Stack: prettyStack,
GoroutineBuckets: buckets,
}
}