昨天把基礎要用的函數與變數搞定了,今天來把錯誤處理完成。
現在定義兩種struct ErrorData與ErrorMeta,分別代表對外表示的錯誤訊息,與原始用於debug的錯誤資訊。
在error.go寫入
import (
"app/setting"
"app/util/debug"
)
// struct of error message to let out
type ErrorDataStruct struct {
Code int `zap:"code"`
Msg string `zap:"msg"`
}
// error message for debug
type ErrorMetaStruct struct {
Msg string
Error error // origin error
Stack []*debug.FuncDataStruct
}
// return error for logger
type ErrorReturn struct {
ErrorData *ErrorDataStruct
ErrorMeta *ErrorMetaStruct
}
func (er *ErrorReturn) Error() string {
return er.ErrorData.Msg
}
// implement Unwrap for error in go 1.13
func (er *ErrorReturn) Unwrap() error {
return er.ErrorMeta.Error
}
func (ems *ErrorMetaStruct) GetStack() []*debug.FuncDataStruct {
return ems.Stack
}
// implement Unwrap for error in go 1.13
func (ems *ErrorMetaStruct) Unwrap() error {
return ems.Error
}
//generate new error data
func NewErrorData(code int, customMsg string) (eData *ErrorDataStruct) {
if customMsg != "" {
eData = &ErrorDataStruct{
Code: code,
Msg: customMsg,
}
} else {
eData = &ErrorDataStruct{
Code: code,
Msg: GetMsg(code),
}
}
return
}
//generate new error meta
func NewErrorMeta(err error, stack []*debug.FuncDataStruct, customMsg string) *ErrorMetaStruct {
return &ErrorMetaStruct{
Msg: customMsg,
Error: err,
Stack: stack,
}
}
// new a error return struct
func NewErrorReturn(code int, err error, stack []*debug.FuncDataStruct, customMsg ...string) *ErrorReturn {
var (
errorData *ErrorDataStruct
errorMeta *ErrorMetaStruct
)
// check error msg
if len(customMsg) > 0 {
errorData = NewErrorData(code, customMsg[0])
} else {
errorData = NewErrorData(code, "")
}
if len(customMsg) > 1 {
errorMeta = NewErrorMeta(err, stack, customMsg[1])
} else {
errorMeta = NewErrorMeta(err, stack, "")
}
return &ErrorReturn{errorData, errorMeta}
}
解釋:
創建handle.go處理error,寫入
package apperr
import (
"app/setting"
"app/util/debug"
"errors"
"github.com/gin-gonic/gin"
)
// return wrap error type
func New(code int, err error, skip int, customMsg ...string) (er error) {
// NewErrorReturn
if setting.Servers["main"].RunMode == gin.DebugMode {
er = NewErrorReturn(code, err, debug.GetCallStack(skip+1), customMsg...) // skip this level
} else {
er = NewErrorReturn(code, err, nil, customMsg...)
}
return
}
// set context.error to handle to front end
// skip will be set for stack level(start from 0)
func ErrorHandle(c *gin.Context, code int, err error, skip int, customMsg ...string) (er *ErrorReturn) {
er = New(code, err, skip, customMsg...).(*ErrorReturn)
_ = c.Error(errors.New("")).SetMeta(er.ErrorData)
return
}
解釋:
現在來寫middleware處理error,如果在運行中發生panic,我們需要recover來避免整個系統掛掉,gin 本身就有提供recovery的middleware可以使用,這邊我直接修改源碼統一把recover與錯誤處理一併解決。
創建middleware package,創建error.go寫入
package middleware
import (
"app/apperr"
"app/logger"
"github.com/gin-gonic/gin"
"net"
"os"
"strings"
)
// ErrorHandling returns a middleware that recovers from any panics and writes a 500 if there was one
// if no panic but there is error in context error, handle for warning to cleint side
func ErrorHandle() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
var brokenPipe bool
if err := recover(); err != nil {
// Check for a broken connection, as it is not really a
// condition that warrants a panic stack trace.
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
}
}
}
// detect error type
if brokenPipe {
// connect error, can't send to front
log.Error(c, apperr.ErrConnectFail, err.(error), 1)
} else {
// system error, need to send to front
log.Error(c, apperr.ErrPermissionDenied, err.(error), 1, "Sorry, something apperr")
}
c.Abort()
}
// if no error
err := c.Errors.Last()
if err == nil {
// logger success
log.Success(c)
return
}
// If the connection is dead, we can't write a status to it.
if !brokenPipe {
// get apperr meta
var (
code int
msg string
)
switch err.Meta.(type) {
case *apperr.ErrorDataStruct:
meta := err.Meta.(*apperr.ErrorDataStruct)
code = meta.Code
msg = meta.Msg
default:
// worng type or something error
code = 1500004
msg = "Sorry, Something error"
log.Warn(c, code, nil, msg)
}
// return to client
_, httpStatus, _ := apperr.SplitCode(code)
c.JSON(httpStatus, gin.H{
"Code": code,
"Msg": msg,
})
}
}()
c.Next()
}
}
解釋:
在router.main,找到r := gin.New()
,在下面補上
r.Use(middleware.ErrorHandle())
這樣就能使用自己寫的error middleware了
目前我們只有處理給client的訊息還沒處理後段自己要看的錯誤資訊,這會在下一篇提到。
目前的專案結構
.
├── app
│ ├── go.mod
│ ├── go.sum
│ ├── main.go
│ ├── config
│ │ └── app
│ │ ├── app.yaml
│ │ └── error.yaml
│ ├── error
│ │ ├── code.go
│ │ ├── error.go
│ │ └── handle.go
│ ├── middleware
│ │ └── error.go
│ ├── router
│ │ ├── host_switch.go
│ │ └── main.go
│ ├── serve
│ │ ├── main.go
│ │ └── main_test.go
│ ├── setting
│ │ └── setting.go
│ └── util
│ └── debug
│ ├── stack.go
│ └── stack_test.go
└── database