go - 异常处理&错误堆栈获取

本文介绍了如何优化Gin框架的异常处理,通过捕获并输出错误堆栈信息,辅助快速定位和排查问题。文章对比了两种方法:直接使用`runtime.Stack`和调用`debug.Stack`,并分析了它们的实现。优化后的异常处理不仅输出堆栈信息,还保留原始错误信息,以提高bug排查效率。
摘要由CSDN通过智能技术生成

背景

最近调整gin项目框架的时候, 想起, 框架的异常处理还没完善, 目前只是把简单的error信息打印到日志里

优化

优化前

package middleware

import (
	"net/http"
	"github.com/gin-gonic/gin"
)

func HandleException() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		defer func() {
			if err := recover(); err != nil {
				// 这里用打印输出做示例
				fmt.Println(err)
				// 接口正常返回, 不把代码的错误信息抛给用户
				ctx.JSON(http.StatusOK, gin.H{
					"code":    -1,
					"message": "出错了"
				})
				return
			}
		}()
		ctx.Next()
	}
}

假设在服务内有这么一段代码

zero := 0
fmt.Println(1 / zero)

上述中间件捕获并输出的错误如下:

runtime error: integer divide by zero

并不利于问题快速定位和bug排查

优化思路

和其他语言一样, 获取到错误的堆栈信息, 输出到日志

优化

于是, 找到以下两种方法

方法1
package main

import (
	"fmt"
	"runtime"
)

func main() {
	defer HandleException()
	num := 0
	fmt.Println(1 / num)
}

func HandleException() {
	errs := recover()
	if errs == nil {
		return
	}
	var stackBuf [1024]byte
	stackBufLen := runtime.Stack(stackBuf[:], false)
	fmt.Printf("==> %s\n", string(stackBuf[:stackBufLen]))
}

结果如下:

==> goroutine 1 [running]:
main.HandleException()
        D:/go/exceptionHandler.go:20 +0x4e
panic({0xef8d60, 0xf9cf50})
        D:/Programs/Go/src/runtime/panic.go:1038 +0x215
main.main()
        D:/go/exceptionHandler.go:11 +0x3b
方法2
package main

import (
	"fmt"
	"runtime"
)

func main() {
	defer HandleException()
	num := 0
	fmt.Println(1 / num)
}

func HandleException() {
	errs := recover()
	if errs == nil {
		return
	}
	fmt.Println(string(debug.Stack()))
}

结果如下:

==> goroutine 1 [running]:
runtime/debug.Stack()
        D:/Programs/Go/src/runtime/debug/stack.go:24 +0x65
main.HandleException()
        D:/go/exceptionHandler.go:19 +0x4e
panic({0xef8d60, 0xf9cf50})
        D:/Programs/Go/src/runtime/panic.go:1038 +0x215
main.main()
        D:/go/exceptionHandler.go:11 +0x3b
总结

都能输出堆栈信息, 但没输出错误信息, 故还需把原来的error输出, 即:

fmt.Println(err)

源码分析

上述两种方法的核心代码如下:

//方法1
var stackBuf [1024]byte
	stackBufLen := runtime.Stack(stackBuf[:], false)
	stackStr := string(stackBuf[:stackBufLen])
//方法2
stackStr := string(debug.Stack())

查看debug.Stack源码, 如下:

// Stack returns a formatted stack trace of the goroutine that calls it.
// It calls runtime.Stack with a large enough buffer to capture the entire trace.
func Stack() []byte {
	buf := make([]byte, 1024)
	for {
		n := runtime.Stack(buf, false)
		if n < len(buf) {
			return buf[:n]
		}
		buf = make([]byte, 2*len(buf))
	}
}

也就是, 方法2其实是对方法1的封装调用而已

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值