go141.net forum.php,recovery.go

本文介绍了一个使用Go语言编写的中间件,Recovery,它能捕获程序运行时的panic并返回500状态码,同时提供了堆栈跟踪。重点讲解了如何处理断开连接和错误日志记录。适合理解Go语言异常处理的最佳实践。
摘要由CSDN通过智能技术生成

// 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"

"fmt"

"io"

"io/ioutil"

"log"

"net"

"net/http"

"net/http/httputil"

"os"

"runtime"

"strings"

"time"

)

var (

dunno = []byte("???")

centerDot = []byte("·")

dot = []byte(".")

slash = []byte("/")

)

// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.

func Recovery() HandlerFunc {

return RecoveryWithWriter(DefaultErrorWriter)

}

// RecoveryWithWriter returns a middleware for a given writer that recovers from any panics and writes a 500 if there was one.

func RecoveryWithWriter(out io.Writer) 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

if ne, ok := err.(*net.OpError); ok {

if se, ok := ne.Err.(*os.SyscallError); ok {

if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {

brokenPipe = true

}

}

}

if logger != nil {

stack := stack(3)

httpRequest, _ := httputil.DumpRequest(c.Request, false)

headers := strings.Split(string(httpRequest), "\r\n")

for idx, header := range headers {

current := strings.Split(header, ":")

if current[0] == "Authorization" {

headers[idx] = current[0] + ": *"

}

}

if brokenPipe {

logger.Printf("%s\n%s%s", err, string(httpRequest), reset)

} else if IsDebugging() {

logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s",

timeFormat(time.Now()), strings.Join(headers, "\r\n"), err, stack, reset)

} else {

logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s",

timeFormat(time.Now()), err, stack, reset)

}

}

// If the connection is dead, we can't write a status to it.

if brokenPipe {

c.Error(err.(error)) // nolint: errcheck

c.Abort()

} else {

c.AbortWithStatus(http.StatusInternalServerError)

}

}

}()

c.Next()

}

}

// 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

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 := ioutil.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 contains 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.Replace(name, centerDot, dot, -1)

return name

}

func timeFormat(t time.Time) string {

timeString := t.Format("2006/01/02 - 15:04:05")

return timeString

}

一键复制

编辑

Web IDE

原始数据

按行查看

历史

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值