github.com/cloudwego/hertz@v0.9.3/pkg/app/middlewares/server/recovery/recovery.go (about) 1 /* 2 * Copyright 2022 CloudWeGo Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package recovery 18 19 import ( 20 "bytes" 21 "context" 22 "fmt" 23 "io/ioutil" 24 "runtime" 25 26 "github.com/cloudwego/hertz/pkg/app" 27 ) 28 29 var ( 30 dunno = []byte("???") 31 centerDot = []byte("·") 32 dot = []byte(".") 33 slash = []byte("/") 34 ) 35 36 // Recovery returns a middleware that recovers from any panic. 37 // By default, it will print the time, content, and stack information of the error and write a 500. 38 // Overriding the Config configuration, you can customize the error printing logic. 39 func Recovery(opts ...Option) app.HandlerFunc { 40 cfg := newOptions(opts...) 41 42 return func(c context.Context, ctx *app.RequestContext) { 43 defer func() { 44 if err := recover(); err != nil { 45 stack := stack(3) 46 47 cfg.recoveryHandler(c, ctx, err, stack) 48 } 49 }() 50 ctx.Next(c) 51 } 52 } 53 54 // stack returns a nicely formatted stack frame, skipping skip frames. 55 func stack(skip int) []byte { 56 buf := new(bytes.Buffer) // the returned data 57 // As we loop, we open files and read them. These variables record the currently 58 // loaded file. 59 var lines [][]byte 60 var lastFile string 61 for i := skip; ; i++ { // Skip the expected number of frames 62 pc, file, line, ok := runtime.Caller(i) 63 if !ok { 64 break 65 } 66 // Print this much at least. If we can't find the source, it won't show. 67 fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc) 68 if file != lastFile { 69 data, err := ioutil.ReadFile(file) 70 if err != nil { 71 continue 72 } 73 lines = bytes.Split(data, []byte{'\n'}) 74 lastFile = file 75 } 76 fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line)) 77 } 78 return buf.Bytes() 79 } 80 81 // source returns a space-trimmed slice of the n'th line. 82 func source(lines [][]byte, n int) []byte { 83 n-- // in stack trace, lines are 1-indexed but our array is 0-indexed 84 if n < 0 || n >= len(lines) { 85 return dunno 86 } 87 return bytes.TrimSpace(lines[n]) 88 } 89 90 // function returns, if possible, the name of the function containing the PC. 91 func function(pc uintptr) []byte { 92 fn := runtime.FuncForPC(pc) 93 if fn == nil { 94 return dunno 95 } 96 name := []byte(fn.Name()) 97 // The name includes the path name to the package, which is unnecessary 98 // since the file name is already included. Plus, it has center dots. 99 // That is, we see 100 // runtime/debug.*T·ptrmethod 101 // and want 102 // *T.ptrmethod 103 // Also the package path might contains dot (e.g. code.google.com/...), 104 // so first eliminate the path prefix 105 if lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 { 106 name = name[lastSlash+1:] 107 } 108 if period := bytes.Index(name, dot); period >= 0 { 109 name = name[period+1:] 110 } 111 name = bytes.Replace(name, centerDot, dot, -1) 112 return name 113 }