golang 中 error 如何影响 log 和 api 状态

查看 52|回复 6
作者:aababc   
标题看起来比较混乱,但总的来说是围绕着 error 的设计问题
所有的示例都是围绕着这个 demo 来讨论, 这个 demo 的大意就想创建一个用户,但是在创建用户之前需要检测一下用户的手机号是否存在
func CreateUser(mobile string) (*User, error) {
        exists, err := mobileExists(mobile)
        if err != nil {
                return nil, err
        }
        if exists {
                return nil, fmt.Errorf("User already exists")
        }
        // ...
}
第一个问题是在 return error 的时候要不要写入日志,代码要不要变成这样
func CreateUser(mobile string) (*User, error) {
        exists, err := mobileExists(mobile)
        if err != nil {
                logger.Errorf("can not close the response", err)
                return nil, err
        }
        if exists {
                return nil, fmt.Errorf("User already exists")
        }
        // ...
}
我得想法总的来说是这样的,要不要把 error 写入日志这个事应该是调用的人来负责,而不是被调用的人来负责,我得想法总的来说 是要么写入日志要么返回错误,而不应该两件事情都干。不知道这个想法对不对?日志的返回这里要不要使用 fmt.Errorf("CreateUser Fail: %w", err) 再返回,扩展开就是什么情况下需要包裹一下
第二个点是关于错误如何和 HTTP 的 Status 关联起来
比如第一个 exists, err := mobileExists(mobile) 这里返回的 err 我希望是一个 HTTP 500 的错误信息,这个点我希望的是非业务层的错误返回 500 比如数据连接失败,redis 连接失败。而且 HTTP 的错误信息还需要返回自己定义的信息。
但是 if exists { return nil, fmt.Errorf("User already exists") } 这个我却希望是一个 HTTP 400 的错误。这个只是举例的这一个 error ,但是内部单纯的业务层面的就有几十个 error 。但是我发现好像不知道怎么做到这一点。
Ayanokouji   
第二点,应该在 handler 层处理,如果你用的是 echo ,
可以 return echo.NewHTTPError( http.StatusUnauthorized, "Please provide valid credentials")
https://echo.labstack.com/docs/error-handling
如果是其他框架,比如 gin ,得先判断 error ,然后 c.JSON( http.StatusUnauthorized, "Please provide valid credentials")
mainjzb   
是否写入日志,应该是应用层最上层的开发去调用,这里我认为不需要。
我认为不需要包裹:fmt.Errorf ,如果需要包裹,也是调用这个函数的人去包裹。这里返回的错误已经清晰明了。
返回 500 还是 400 可以用 errors.as 或 errors.is 去判断,这里的情况应该在 redis 连接部分定义 InternelServerError 返回后用 errors.as 判断。
type InternelServerError  struct{
     msg  string
}
func (e InternelServerError ) Error() string {
        return fmt.Sprintf("%v",  e.msg)
}
Goooooos   
能拿到堆栈的前提下,第一点日志没什么必要
soul11201   
return 的两个 error 要不要细分,细分的话,用哨兵比较合适
个人经验,仅供参考
1.日志不要再中间链路打印,有可能会冗余
2.错误如果比较多,用哨兵,如果比较深,用包裹。又深又多,哨兵+错误链包裹
主要目标还是看你目标是什么,比如要把多少异常信息传递给上层、要不要基于底层错误信息在上层做控制处理
rower   
第一个对于 web 的错误,比较好的做法是创建一个 Error 的中间件统一处理,在 gin 中,我的用法如下
func CreateUser(mobile string) (*User) {
        exists, err := mobileExists(mobile)
        if err != nil {
        // 这个 c 是 gin 的 context ,一般 mobile 这个请求参数是从 c 获得的,这里忽略那些细节,记录错误就是 c.Error()      
                c.Error(err)
                return nil
        }
        if exists {
                c.Error(err)
               return nil
        }
        // ...
}
// 中间件处理错误
func Errors(log *logger.Logger) gin.HandlerFunc {
        return func(c *gin.Context) {
   
        ctx := c.Request.Context()
        
                      if len(c.Errors) > 0 {
                        // 处理第一个错误
                        // 在 gin 中,错误是一个数组,这里只处理第一个错误,一般来说我们在程序中遇到错误时,只会返回一个错误
                        // 如果出现了例外情况,那么我们需要修改这里的代码
                        err := c.Errors[0].Err
                        // 记录错误
                        log.Error(ctx, "message", "ERROR", err.Error())
                        }
}
rower   
第二点,就是首先需要有自己的自定义错误类型,参考
https://github.com/ardanlabs/service6-video/tree/main/app/api/errs
然后每种错误对应的 http 状态码
参考
https://github.com/ardanlabs/service6-video/blob/main/api/http/api/mid/errors.go
的 init() 函数
最后在 Error 的中间件中对错误进行判断,如果是自定义错误,返回错误和对应的状态码,如果不是,返回 500
您需要登录后才可以回帖 登录 | 立即注册

返回顶部