/
recovery.go
189 lines (176 loc) · 6.64 KB
/
recovery.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
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package gin
import (
"bytes"
"errors"
"fmt"
"io"
"log"
"net"
"net/http"
"net/http/httputil"
"os"
"runtime"
"strings"
"time"
)
var (
dunno = []byte("???")
centerDot = []byte("·")
dot = []byte(".")
slash = []byte("/")
)
// RecoveryFunc defines the function passable to CustomRecovery.
type RecoveryFunc func(c *Context, err any)
// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
// Recovery 中间件用于捕获处理流程中出现 panic 的错误
// 如果连接未断开, 则返回 500 错误响应
func Recovery() HandlerFunc {
return RecoveryWithWriter(DefaultErrorWriter)
}
// CustomRecovery returns a middleware that recovers from any panics and calls the provided handle func to handle it.
func CustomRecovery(handle RecoveryFunc) HandlerFunc {
return RecoveryWithWriter(DefaultErrorWriter, handle)
}
// RecoveryWithWriter returns a middleware for a given writer that recovers from any panics and writes a 500 if there was one.
// 使用传递的 out 对 Recovery 中间件进行装饰
func RecoveryWithWriter(out io.Writer, recovery ...RecoveryFunc) HandlerFunc {
if len(recovery) > 0 {
return CustomRecoveryWithWriter(out, recovery[0])
}
return CustomRecoveryWithWriter(out, defaultHandleRecovery)
}
// CustomRecoveryWithWriter returns a middleware for a given writer that recovers from any panics and calls the provided handle func to handle it.
// Recovery 中间件
// RecoveryWithWriter(out io.Writer) 函数仅为了对最终返回的中间件 HandlerFunc 函数进行装饰
//
// 在该中间件中,可分为两个逻辑块,一个是 defer,一个是 Next()
// Next() 与 Logger() 中间件中的 Next() 作用类似
// defer 中使用 recover() 来捕获在后续中间件中 panic 的错误信息,并对该错误信息进行处理。
//
// 在该中间件中,首先是判断当前连接是否已中断,然后是进行相关的日志处理,最后,如果连接已中断,则直接设置错误信息,并终止该上下文
// 否则,终止该上下文并返回 500 错误响应。
func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
var logger *log.Logger
if out != nil {
logger = log.New(out, "\n\n\x1b[31m", log.LstdFlags)
}
return func(c *Context) {
defer func() {
if err := recover(); err != nil {
// Check for a broken connection, as it is not really a
// condition that warrants a panic stack trace.
var brokenPipe bool // 用于标记连接是否断开:底层连接异常,则标记 brokenPipe
if ne, ok := err.(*net.OpError); ok { // 从错误信息中判断连接是否断开
var se *os.SyscallError
if errors.As(ne, &se) {
seStr := strings.ToLower(se.Error())
if strings.Contains(seStr, "broken pipe") ||
strings.Contains(seStr, "connection reset by peer") {
brokenPipe = true
}
}
}
if logger != nil { // 日志打印:使用了一个 DumpRequest 来输出请求中的数据,包括了 HTTP header 头和 HTTP Body
stack := stack(3) // 打印具体的堆栈信息
httpRequest, _ := httputil.DumpRequest(c.Request, false)
headers := strings.Split(string(httpRequest), "\r\n")
for idx, header := range headers { // 如果 header 中有认证信息,则隐藏不打印
current := strings.Split(header, ":")
if current[0] == "Authorization" { // 隐藏 HTTP 请求认证信息
headers[idx] = current[0] + ": *"
}
}
headersToStr := strings.Join(headers, "\r\n")
if brokenPipe {
logger.Printf("%s\n%s%s", err, headersToStr, reset)
} else if IsDebugging() {
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s",
timeFormat(time.Now()), headersToStr, err, stack, reset)
} else {
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s",
timeFormat(time.Now()), err, stack, reset)
}
}
if brokenPipe { // 如果连接已断开, 则已经无法为其写入状态码
// If the connection is dead, we can't write a status to it.
// 添加错误信息至上下文中, 用于日志输出
c.Error(err.(error)) //nolint: errcheck
c.Abort() // 终止该上下文
} else { // 连接未断开
handle(c, err) // 终止该上下文并写入 500 错误状态码
}
}
}()
c.Next() // 继续执行下一个中间件
}
}
func defaultHandleRecovery(c *Context, _ any) {
c.AbortWithStatus(http.StatusInternalServerError)
}
// stack returns a nicely formatted stack frame, skipping skip frames.
func stack(skip int) []byte {
buf := new(bytes.Buffer) // the returned data
// As we loop, we open files and read them. These variables record the currently
// loaded file.
var lines [][]byte
var lastFile string
// 循环从第 skip 层堆栈到最后一层
for i := skip; ; i++ { // Skip the expected number of frames
pc, file, line, ok := runtime.Caller(i)
if !ok {
break
}
// Print this much at least. If we can't find the source, it won't show.
fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
if file != lastFile { // 获取堆栈函数所在源文件
data, err := os.ReadFile(file)
if err != nil {
continue
}
lines = bytes.Split(data, []byte{'\n'})
lastFile = file
}
// 打印源代码内容
fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
}
return buf.Bytes()
}
// source returns a space-trimmed slice of the n'th line.
func source(lines [][]byte, n int) []byte {
n-- // in stack trace, lines are 1-indexed but our array is 0-indexed
if n < 0 || n >= len(lines) {
return dunno
}
return bytes.TrimSpace(lines[n])
}
// function returns, if possible, the name of the function containing the PC.
func function(pc uintptr) []byte {
fn := runtime.FuncForPC(pc)
if fn == nil {
return dunno
}
name := []byte(fn.Name())
// The name includes the path name to the package, which is unnecessary
// since the file name is already included. Plus, it has center dots.
// That is, we see
// runtime/debug.*T·ptrmethod
// and want
// *T.ptrmethod
// Also the package path might contain dot (e.g. code.google.com/...),
// so first eliminate the path prefix
if lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 {
name = name[lastSlash+1:]
}
if period := bytes.Index(name, dot); period >= 0 {
name = name[period+1:]
}
name = bytes.ReplaceAll(name, centerDot, dot)
return name
}
// timeFormat returns a customized time string for logger.
func timeFormat(t time.Time) string {
return t.Format("2006/01/02 - 15:04:05")
}