github.com/hellobchain/third_party@v0.0.0-20230331131523-deb0478a2e52/gin/recovery.go (about)

     1  // Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
     2  // Use of this source code is governed by a MIT style
     3  // license that can be found in the LICENSE file.
     4  
     5  package gin
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"log"
    13  	"net"
    14  	"github.com/hellobchain/newcryptosm/http"
    15  	"github.com/hellobchain/newcryptosm/http/httputil"
    16  	"os"
    17  	"runtime"
    18  	"strings"
    19  	"time"
    20  )
    21  
    22  var (
    23  	dunno     = []byte("???")
    24  	centerDot = []byte("·")
    25  	dot       = []byte(".")
    26  	slash     = []byte("/")
    27  )
    28  
    29  // RecoveryFunc defines the function passable to CustomRecovery.
    30  type RecoveryFunc func(c *Context, err interface{})
    31  
    32  // Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
    33  func Recovery() HandlerFunc {
    34  	return RecoveryWithWriter(DefaultErrorWriter)
    35  }
    36  
    37  //CustomRecovery returns a middleware that recovers from any panics and calls the provided handle func to handle it.
    38  func CustomRecovery(handle RecoveryFunc) HandlerFunc {
    39  	return RecoveryWithWriter(DefaultErrorWriter, handle)
    40  }
    41  
    42  // RecoveryWithWriter returns a middleware for a given writer that recovers from any panics and writes a 500 if there was one.
    43  func RecoveryWithWriter(out io.Writer, recovery ...RecoveryFunc) HandlerFunc {
    44  	if len(recovery) > 0 {
    45  		return CustomRecoveryWithWriter(out, recovery[0])
    46  	}
    47  	return CustomRecoveryWithWriter(out, defaultHandleRecovery)
    48  }
    49  
    50  // CustomRecoveryWithWriter returns a middleware for a given writer that recovers from any panics and calls the provided handle func to handle it.
    51  func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
    52  	var logger *log.Logger
    53  	if out != nil {
    54  		logger = log.New(out, "\n\n\x1b[31m", log.LstdFlags)
    55  	}
    56  	return func(c *Context) {
    57  		defer func() {
    58  			if err := recover(); err != nil {
    59  				// Check for a broken connection, as it is not really a
    60  				// condition that warrants a panic stack trace.
    61  				var brokenPipe bool
    62  				if ne, ok := err.(*net.OpError); ok {
    63  					if se, ok := ne.Err.(*os.SyscallError); ok {
    64  						if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
    65  							brokenPipe = true
    66  						}
    67  					}
    68  				}
    69  				if logger != nil {
    70  					stack := stack(3)
    71  					httpRequest, _ := httputil.DumpRequest(c.Request, false)
    72  					headers := strings.Split(string(httpRequest), "\r\n")
    73  					for idx, header := range headers {
    74  						current := strings.Split(header, ":")
    75  						if current[0] == "Authorization" {
    76  							headers[idx] = current[0] + ": *"
    77  						}
    78  					}
    79  					headersToStr := strings.Join(headers, "\r\n")
    80  					if brokenPipe {
    81  						logger.Printf("%s\n%s%s", err, headersToStr, reset)
    82  					} else if IsDebugging() {
    83  						logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s",
    84  							timeFormat(time.Now()), headersToStr, err, stack, reset)
    85  					} else {
    86  						logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s",
    87  							timeFormat(time.Now()), err, stack, reset)
    88  					}
    89  				}
    90  				if brokenPipe {
    91  					// If the connection is dead, we can't write a status to it.
    92  					c.Error(err.(error)) // nolint: errcheck
    93  					c.Abort()
    94  				} else {
    95  					handle(c, err)
    96  				}
    97  			}
    98  		}()
    99  		c.Next()
   100  	}
   101  }
   102  
   103  func defaultHandleRecovery(c *Context, err interface{}) {
   104  	c.AbortWithStatus(http.StatusInternalServerError)
   105  }
   106  
   107  // stack returns a nicely formatted stack frame, skipping skip frames.
   108  func stack(skip int) []byte {
   109  	buf := new(bytes.Buffer) // the returned data
   110  	// As we loop, we open files and read them. These variables record the currently
   111  	// loaded file.
   112  	var lines [][]byte
   113  	var lastFile string
   114  	for i := skip; ; i++ { // Skip the expected number of frames
   115  		pc, file, line, ok := runtime.Caller(i)
   116  		if !ok {
   117  			break
   118  		}
   119  		// Print this much at least.  If we can't find the source, it won't show.
   120  		fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
   121  		if file != lastFile {
   122  			data, err := ioutil.ReadFile(file)
   123  			if err != nil {
   124  				continue
   125  			}
   126  			lines = bytes.Split(data, []byte{'\n'})
   127  			lastFile = file
   128  		}
   129  		fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
   130  	}
   131  	return buf.Bytes()
   132  }
   133  
   134  // source returns a space-trimmed slice of the n'th line.
   135  func source(lines [][]byte, n int) []byte {
   136  	n-- // in stack trace, lines are 1-indexed but our array is 0-indexed
   137  	if n < 0 || n >= len(lines) {
   138  		return dunno
   139  	}
   140  	return bytes.TrimSpace(lines[n])
   141  }
   142  
   143  // function returns, if possible, the name of the function containing the PC.
   144  func function(pc uintptr) []byte {
   145  	fn := runtime.FuncForPC(pc)
   146  	if fn == nil {
   147  		return dunno
   148  	}
   149  	name := []byte(fn.Name())
   150  	// The name includes the path name to the package, which is unnecessary
   151  	// since the file name is already included.  Plus, it has center dots.
   152  	// That is, we see
   153  	//	runtime/debug.*T·ptrmethod
   154  	// and want
   155  	//	*T.ptrmethod
   156  	// Also the package path might contains dot (e.g. code.google.com/...),
   157  	// so first eliminate the path prefix
   158  	if lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 {
   159  		name = name[lastSlash+1:]
   160  	}
   161  	if period := bytes.Index(name, dot); period >= 0 {
   162  		name = name[period+1:]
   163  	}
   164  	name = bytes.Replace(name, centerDot, dot, -1)
   165  	return name
   166  }
   167  
   168  func timeFormat(t time.Time) string {
   169  	timeString := t.Format("2006/01/02 - 15:04:05")
   170  	return timeString
   171  }