首页 > 作文

Gin框架 – 自定义错误处理

更新时间:2023-04-07 21:25:45 阅读: 评论:0

概述

很多读者在后台向我要 gin 框架实战系列的 demo 源码,在这里再说明一下,源码我都更新到 github 上,地址:https://github.com/xinliangnote/go

开始今天的文章,为什么要自定义错误处理?默认的错误处理方式是什么?

那好,咱们就先说下默认的错误处理。

默认的错误处理是 errors.new(“错误信息”),这个信息通过 error 类型的返回值进行返回。

举个简单的例子:

func hello(name string) (str string, err error) {            if name == "" {                err = errors.new("name 不能为空")                return            }            str = fmt.sprintf("hello: %s", name)            return        }

当调用这个方法时:

  var name = ""        str, err :=  hello(name)        if err != nil {            fmt.println(err.error())            return        }

这就是默认的错误处理,下面还会用这个例子进行说。

这个默认的错误处理,只是得到了一个错误信息的字符串。

然而…

我还想得到发生错误时的 时间、 文件名、 方法名、 行号 等信息。

我还想得到错误时进行告警,比如 短信告警、 邮件告警、 微信告警 等。

我还想调用的时候,不那么复杂,就和默认错误处理类似,比如:

alarm.wechat(“错误信息”)
return

这样,我们就得到了我们想要的信息( 时间、 文件名、 方法名、 行号),并通过 微信 的方式进行告警通知我们。

同理, alarm.email(“错误信息”)、 alarm.sms(“错误信息”) 我们得到的信息是一样的,只是告警方式不同而已。

还要保证,我们业务逻辑中,获取错误的时候,只获取错误信息即可。

上面这些想出来的,就是今天要实现的,自定义错误处理,我们就实现之前,先说下 go 的错误处理。

错误处理

  package main        import (            "errors"            "fmt"        )        func hello(name string) (str string, err error) {            if name == "" {                err = errors.new("name 不能为空")                return            }            str = fmt.sprintf("hello: %s", name)            return        }        func main() {            var name = ""            fmt.println("param:", name)            str, err := hello(name)            if err != nil {                fmt.println(err.error())                return            }            fmt.println(str)        }

输出:

    param: tom        hello: tom

当 name = “” 时,输出:

  param:        name 不能为空

建议每个函数都要有错误处理,error 应该为最后一个返回值。

咱们一起看下官方 errors.go

  // copyright 2011 the go authors. all rights rerved.        // u of this source code is governed by a bsd-style        // licen that can be found in the licen file.        // package errors implements functions to manipulate errors.        package errors        // new returns an error that formats as the given text.        func new(text string) error {            return &errorstring{text}        }        // errorstring is a trivial implementation of error.        type errorstring struct {            s string        }        func (e *errorstring) error() string {            return e.s        }

上面的代码,并不复杂,参照上面的,咱们进行写一个自定义错误处理。

自定义错误处理

咱们定义一个 alarm.go,用于处理告警。

废话不多说,直接看代码。

 package alarm        import (            "encoding/json"            "fmt"            "gindemo/common/function"            "path/filepath"            "runtime"            "strings"        )        type errorstring struct {            s string        }        type errorinfo struct {            time     string `json:"time"`            alarm    string `json:"alarm"`            message  string `json:"message"`            filename string `json:"filename"`            line     int    `json:"line"`            funcname string `json:"funcname"`        }        func (e *errorstring) error() string {            return e.s        }        func new (text string) error {        我的中国梦作文    alarm("info", text)            return &errorstring{text}        }        // 发邮件        func email (text string) error {            alarm("email", text)            return &errorstring{text}        }        // 发短信        func sms (text string) error {            alarm("sms", text)            return &errorstring{text}        }        // 发微信        func wechat (text string) error {            alarm("wx", text)            return &errorstring{text}        }        // 告警方法        func  alarm(level string, str string) {            // 当前时间            currenttime := function.gettimestr()            // 定义 文件名、行号、方法名            filename, line, functionname := "?", 0 , "?"            pc, filename, line, ok := runtime.caller(2)            if ok {                functionname = runtime.funcforpc(pc).name()                functionname = filepath.ext(functionname)                functionname = strings.trimprefix(functionname, ".")            }            var msg = errorinfo {                time     : currenttime,                alarm    : level,                message  : str,                filename : filename,                line     : line,                funcname : functionname,            }            jsons, errs := json.marshal(msg)            if errs != nil {                fmt.println("json marshal error:", errs)            }            errorjsoninfo := string(jsons)            fmt.println(errorjsoninfo)            if level == "email" {                // 执行发邮件            } el if level == "sms" {                // 执行发短信            } el if level == "wx" {                // 执行发微信            } el if level == "info" {                // 执行记日志            }        }

看下如何调用:

 package v1        import (            "fmt"            "gindemo/common/alarm"            "gindemo/entity"            "github.com/gin-gonic/gin"            "net/http"        )        func addproduct(c *gin.context)  {            // 获取 get 参数            name := c.query("name")            var res = entity.result{}            str, err := hello(name)            if err != nil {                res.tcode(entity.code_error)                res.tmessage(err.error())                c.json(http.statusok, res)                c.abort()                return            }            res.tcode(entity.code_success)            res.tmessage(str)            c.json(http.statusok, res)        }    澳洲净宗学院    func hello(name string) (str string, err error) {            if name == "" {                err = alarm.wechat("name 不能为空")                return            }            str = fmt.sprintf("hello: %s", name)            return        }

访问:http://localhost:8080/v1/product/add?name=a    {            "code": 1,            "msg": "hello: a",            "data": null        }

未抛出错误,不会输出信息。

访问:http://localhost:8080/v1/product/add    {            "code": -1,            "msg": "name 不能为空",            "data": null        }

抛出了错误,输出信息如下:

{“time”:”2019-07-23 22:19:17″,”alarm”:”wx”,”message”:”name 不能为空”,”filename”:”绝对路径/gindemo/router/v1/product.go”,”line”:33,”funcname”:”hello”}

可能这会有同学说:“用上一篇分享的数据绑定和验证,将传入的参数进行 binding:”required” 也可以实现呀”。

我只能说:“同学呀,你不理解我的良苦用心,这只是个例子,大家可以在一些复杂的业务逻辑判断场景中使用自定义错误处理”。

到这里,报错时我们收到了 时间、 错误信息、 文件名、 行号、 方法名 了。

调用起来,也比较简单。

虽然标记了告警方式,还是没有进行告警通知呀。

我想说,在这里存储数据到队列中,再执行异步任务具体去消耗,这块就不实现了,大家可以去完善。

读取 文件名、 方法名、 行号 使用的是 runtime.caller()。

我们还知道,go 有 panic 和 recover,它们是干什么的呢,接下来咱们就说说。

panic 和 recover

当程序不能继续运行的时候,才应该使用 panic 抛出错误。

当程序发生 panic 后,在 defer(延迟函数) 内部可以调用 recover 进行控制,不过有个前提条件,只有在相同的 go 协程中才可以。

panic 分两个,一种是有意抛出的,一种是无意的写程序马虎造成的,咱们一个个说。

有意抛出的 panic:

 package main        import (            "fmt"        )        func main() {            fmt.println("-- 1 --")            defer func() {                if r := recover(); r != nil {                    fmt.printf("panic: %s\n", r)                }                fmt.println("-- 2 --")            }()            panic("i am panic")        }

输出:

   -- 1 --        panic: i am panic        -- 2 --

无意抛出的 panic:

 package main        import (            "fmt"        )        func main() {            fmt.println("-- 1 --")            defer func() {                if r := recover(); r != nil {              stormii      fmt.printf("panic: %s\n", r)                }                fmt.println("-- 2 --")            }()            var slice = [] int {1, 2, 3, 4, 5}            slice[6] = 6        }

输出:

  -- 1 --        panic: runtime error: index out of range        -- 2 --

上面的两个我们都通过 recover 捕获到了,那我们如何在 gin 框架中使用呢?如果收到 panic 时,也想进行告警怎么实现呢?

既然想实现告警,先在 ararm.go 中定义一个 panic() 方法,当项目发生 panic 异常时,调用这个方法,这样就实现告警了。

   // panic 异常        func panic (text string) error {            alarm("pani铜在空气中加热c", text)            return &errorstring{text}        }

那我们怎么捕获到呢?

使用中间件进行捕获,写一个 recover 中间件。

    package recover        import (            "fmt"            "gindemo/common/alarm"            "github.com/gin-gonic/gin"        )        func recover()  gin.handlerfunc {            return func(c *gin.context) {                defer func() {                    if r := recover(); r != nil {                        alarm.panic(fmt.sprintf("%s", r))                    }                }()                c.next()            }        }

路由调用中间件:

    r.u(logger.loggertofile(), recover.recover())        //u 可以传递多个中间件。

验证下吧,咱们先抛出两个异常,看看能否捕获到?

还是修改 product.go 这个文件吧。

有意抛出 panic:

package v1        import (            "fmt"            "gindemo/entity"            "github.com/gin-gonic/gin"            "net/http"        )        func addproduct(c *gin.context)  {            // 获取 get 参数            name := c.query("name")            var res = entity.result{}            str, err := hello(name)            if err != nil {                res.tcode(entity.code_error)                res.tmessage(err.error())                c.json(http.statusok, res)                c.abort()                return            }            res.tcode(entity.code_success)            res.tmessage(str)            c.json(http.statusok, res)        }        func hello(name string) (str string, err error) {            if name == "" {                // 有意抛出 panic                panic("i am panic")                return            }            str = fmt.sprintf("hello: %s", name)            return        }

访问:http://localhost:8080/v1/product/add

界面是空白的。

抛出了异常,输出信息如下:

{“time”:”2019-07-23 22:42:37″,”alarm”:”panic”,”message”:”i am panic”,”filename”:”绝对路径/gindemo/middleware/recover/recover.go”,”line”:13,”funcname”:”1″}

很显然,定位的文件名、方法名、行号不是我们想要的。

需要调整 runtime.caller(2),这个代码在 alarm.go的alarm 方法中。

将 2 调整成 4 ,看下输出信息:

{“time”:”2019-07-23 22:45:24″,”alarm”:”panic”,”message”:”i am panic”,”filename”:”绝对路径/gindemo/router/v1/product.go”,”line”:33,”funcname”:”hello”}

这就对了。

无意抛出 panic:

   // 上面代码不变        func hello(name string) (str string, err error) {            if name == "" {                // 无意抛出 panic                var slice = [] int {1, 2, 3, 4, 5}                slice[6] = 6                return            }            str = fmt.sprintf("hello: %s", name)            return        }

访问:http://localhost:8080/v1/product/add

界面是空白的。

抛出了异常,输出信息如下:

{“time”:”2019-07-23 22:50:06″,”alarm”:”panic”,”message”:”runtime error: index out of range”,”filename”:”绝对路径/runtime/panic.go”,”line”:44,”funcname”:”panicindex”}

很显然,定位的文件名、方法名、行号也不是我们想要的。

将 4 调整成 5 ,看下输出信息:

{“time”:”2019-07-23 22:55:27″,”alarm”:”panic”,”message”:”runtime error: index out of range”,”filename”:”绝对路径/gindemo/router/v1/product.go”,”line”:34,”funcname”:”hello”}

这就对了。

奇怪了,这是为什么?

在这里,有必要说下 runtime.caller(skip) 了。

skip 指的调用的深度。

为 0 时,打印当前调用文件及行数。

为 1 时,打印上级调用的文件及行数。

依次类推…

在这块,调用的时候需要注意下,我现在还没有好的解决方案。

我是将 skip(调用深度),当一个参数传我最喜欢的电视栏目递进去。

比如:

    // 发微信        func wechat (text string) error {            alarm("wx", text, 2)            return &errorstring{text}        }        // panic 异常        func panic (text string) error {            alarm("panic", text, 5)            return &errorstring{text}        }

具体的代码就不贴了。

但是,有意抛出 panic 和 无意抛出 panic 的调用深度又不同,怎么办?

1、尽量将有意抛出的 panic 改成抛出错误的方式。

2、想其他办法搞定它。

本文发布于:2023-04-07 21:25:29,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/zuowen/38c34c5e31229f909689f0e8940e5c97.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

本文word下载地址:Gin框架 – 自定义错误处理.doc

本文 PDF 下载地址:Gin框架 – 自定义错误处理.pdf

标签:抛出   行号   错误   法名
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图