V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
Ayanokouji
V2EX  ›  Go 编程语言

求教: golang error 和 log 的最佳实践思路

  •  
  •   Ayanokouji · 3 天前 · 2982 次点击
    用 go 写业务时,遇到 error 时,大多数情况只能一路向上 return err ,我们基于这个场景讨论。

    这个场景,和 java 写业务遇到的 checked exception 很类似,写代码时,只能一路向上 throw (或者 catch 住 throw new unchecked exception ),最终由框架统一处理。

    如果遇到 go 遇到经验不足的开发者(比如以前的我),就会看到这样的错误日志:

    Error: unexpected '>' at the beginning of value

    Error: EOF

    Error: wrong argument value

    嗯。。。这 tm 是啥啊,到底是哪一行代码出错啊。

    调用链越长,问题越难排查。

    比较通用的 web 业务调用链,一般是 handler -> service -> 中间件(数据库/redis/第三方 api 等)

    随着坑踩的多了,现在遇到 err, 一般是 return fmt.Errorf("xxx:%w", err)

    日志一般在 handler 层 slog.log("xxx", slog.Any("request", req), slog.Any("err", err))

    但是缺少了调用栈,总觉得少了点什么。

    请教下各位,如何平衡 error 和 log ,主要是服务于问题排查。

    看过 echo 和 huma 的框架 error 处理,都是自定义 err ,框架统一处理

    ------

    ps:那些上来只踩一脚 java 的,还是去多写写代码吧,这种 err ( unexpected '>' at the beginning of value ) 真的比 excetiop (含调用栈) 强吗。
    81 条回复    2025-01-03 08:28:33 +08:00
    pluswu1986
        1
    pluswu1986  
       3 天前
    业务代码一般不打印日志, 中间件统一在入口处理,底层错误返回 error.Warp 足够的信息(callstack 等) 让中间件统一处理
    Ayanokouji
        2
    Ayanokouji  
    OP
       3 天前
    @pluswu1986 用的是 github.com/pkg/errors 这个库吗,但是这个已经不更新了
    csys
        3
    csys  
       3 天前
    没懂你这啥问题

    go 记录 error 日志,怎么就没有调用栈了
    如果 r 缺少调用栈,那是日志库有问题或者用法有问题
    harleyliao
        4
    harleyliao  
       3 天前
    一般是在每个出错的地方都打印错误日志, 并返回错误给上一层, 这样通过错误日志就可以推测出调用链了.
    Ayanokouji
        5
    Ayanokouji  
    OP
       3 天前
    @harleyliao
    @csys 日志是能说明是哪一行的。

    如果遇到 error 就打印,这样日志太多了吧,比如 sevice 层,查了 5 次 sql ,那就需要写打印五次日志吗。
    bli22ard
        6
    bli22ard  
       3 天前
    可能是最佳实战的做法是,你调用了标准库,或者第三方库,这些库返回 error 之后,你应该先用一些第三方 error 库 wrap 一下,主要目的是记录一下 error 发生的调用栈,这样上层什么位置拿到 error ,都能打印出来这个 error 是哪个位置发生的。还有一种,目的类似的做法,不记录调用栈,而改为附加一个错误码进去,这样上层的任何调用者也可以知道 error 哪里发生的,不过维护错误码这种方式维护时间越长,越容易搞混乱,导致排查问题困难。
    原则上,只要不是本工程的代码生成的 error 都进行一次 wrap ,然后向上 return ,如果没有最上层,则进行错误日志打印。
    Ayanokouji
        7
    Ayanokouji  
    OP
       3 天前
    @bli22ard 嗯,这种做法比较认同的。第三方 error 库不太好找,有推荐的吗 github.com/pkg/errors 这个已经停更了。
    还有为了兼容 slog ,可能还需要封装一些代码。
    dylanqqt
        8
    dylanqqt  
       3 天前
    @Ayanokouji 我们就是遇到 err 就打印,“查五次”就要打印五次的 err ,要写五次 if err != nil ,因为可能第一个 err 会影响第二次的查询,不判断没法往下走啊
    Ayanokouji
        9
    Ayanokouji  
    OP
       3 天前
    @dylanqqt 不能往下走是没问题的,就是五次 if err != nil 里边还有写五次打印日志,遇到错误也只能是其中一处,实际打印的日志也是一次
    csys
        10
    csys  
       2 天前   ❤️ 1
    @Ayanokouji

    懂你意思了

    > 如果遇到 error 就打印
    多数情况下是这样的,我自己是倾向于把所有 error 都打印出来,除非某个地方什么都不做,只是把 error 向上传递,也就是说 1. 产生 error 的地方记录日志 2. 处理 error 的地方记录日志

    你遇到的这个问题,看起来更像是需要一个链路追踪 https://opentelemetry.io/docs/languages/go/instrumentation/

    至于直接在 error 里记录完整的调用栈也是可以的 https://pkg.go.dev/github.com/cockroachdb/errors
    dylanqqt
        11
    dylanqqt  
       2 天前
    @Ayanokouji 那就是在最底层的那个 err 打印吧
    Ayanokouji
        12
    Ayanokouji  
    OP
       2 天前
    @dylanqqt 嗯,就是最底层其实不是那么好界定,写法也比较啰嗦。不过这样的日志确实能当调用栈串联起来
    bli22ard
        13
    bli22ard  
       2 天前
    @Ayanokouji github.com/pkg/errors 这个库还能再战。其实自己定义也可以,实现 error 的 struct ,增加一个 Wrap 函数,参数接受一个 error ,函数体,runtime 拿调用栈,将结果保存在 struct 的成员变量,这样就可以用了
    qW7bo2FbzbC0
        14
    qW7bo2FbzbC0  
       2 天前
    ```go
    err := doJob()
    if err != nil {
    return fmt.Errorf("do job failed, %w", err)
    }
    ```
    Ayanokouji
        15
    Ayanokouji  
    OP
       2 天前
    @qW7bo2FbzbC0 我目前就是这种做法,这种做法的缺陷是,前缀的 message 需要足够清晰和唯一,清晰是为了可读,唯一是为了代码搜索定位当调用栈来用。
    还有如果项目做国际化,这种需要定义错误吗,维护比较费精力。
    lbp0200
        16
    lbp0200  
       2 天前
    这个和语言无关
    错误的地方记录日志,包括当前行,当前文件
    上级代码只需要知道有错误就行
    zacard
        17
    zacard  
       2 天前
    和 java 的异常处理类似,底层库/函数用 errors 包装携带堆栈,上层统一捕获 error 打印即可。github.com/pkg/errors 虽然不更新了,但是够用了
    guanzhangzhang
        18
    guanzhangzhang  
       2 天前
    两种,一种是 wrap 一下信息,我博客很多文章搜报错能直接搜到开源项目源码里去,另一种是 zap 这种 logger 能打印带文件 go:行数的
    Kauruus
        19
    Kauruus  
       2 天前
    你在 error 里带上调用栈也不是不行(例如 https://github.com/go-errors/errors ),然后在顶层处理,打印或者发到 sentry 。
    z1829909
        20
    z1829909  
       2 天前   ❤️ 1
    我一般也是每层返回的时候包一些关键字, 相当于人肉造了一个栈.
    xxlxiaxiaolei
        21
    xxlxiaxiaolei  
       2 天前
    @guanzhangzhang 真张馆长?我还有你 QQ 呢
    Linxing
        22
    Linxing  
       2 天前
    WithStack
    pkoukk
        23
    pkoukk  
       2 天前
    一般我不会携带堆栈信息,太多了,去获取当前堆栈的资源消耗也太重了。
    其实很简单啊,error 是个 interface ,return error 的时候变成 retrun MyError(code,err)就行了
    额外附加一个 code 足矣.
    这是个简单的示例,实际上还可以做很多事情在里面

    func MyError(code int, err error) error {
    if errors.As(err, &myError{}) {
    return err
    } else {
    return &myError{code: code, source: err}
    }
    }
    lifei6671
        24
    lifei6671  
       2 天前
    @Ayanokouji #2 golang 的 1.21 可以直接用%w 来包裹 error ,也可以用 errors.join 来合并多个 error 。不需要第三方库了。
    pkoukk
        25
    pkoukk  
       2 天前
    对你的 APPEND 的回复:
    那你直接用 panic ,那玩意里面自带堆栈,去上游 revocer 。
    喜欢 try catch 的,用 panic recover 去
    Nazz
        26
    Nazz  
       2 天前
    我的做法是用 github.com/pkg/errors 带上错误堆栈, 用 zerolog 设置错误级别, 在响应函数里面写入日志, lumberjack 做日志切分
    Ayanokouji
        27
    Ayanokouji  
    OP
       2 天前
    @pkoukk 你是二极管吗,要看调用栈就得用 panic 啊,你家写 go 全是 panic 啊。前面写那么多看了吗,还是说你写过 go 吗。有能耐把你的解决方法写出来啊。
    Vitumoc
        28
    Vitumoc  
       2 天前
    没有特别看懂 OP 想要表达什么。

    如果只是想要调用栈的话,并不困难啊?

    大概就这样:

    ```go
    import "runtime/debug"

    func main() {
    defer func() {
    stack := debug.Stack()
    fmt.Sprintf("调用栈信息:\n%s", stack)
    }
    }
    ```

    实际用的时候再做一些封装
    Ayanokouji
        29
    Ayanokouji  
    OP
       2 天前
    @Vitumoc 不是非必要用调用栈,一切为了问题排查,就比如日志突兀的出现一句:Error: unexpected '>' at the beginning of value 。这种情况知道是 json 错误,但是哪里发生的 json 错误,参数是什么之类的,定位解决问题的时候,很困难
    pkoukk
        30
    pkoukk  
       2 天前
    @Ayanokouji #27 我写了,你没看,你才是二极管
    pkoukk
        31
    pkoukk  
       2 天前
    @Ayanokouji #27 这么简单的解法都想不出来,怀疑你的水平
    Ayanokouji
        32
    Ayanokouji  
    OP
       2 天前
    @pkoukk 所以你家 go ,全是 panic recover ? java 的 try catch = go 的 panic recover ?
    p1gd0g
        33
    p1gd0g  
       2 天前
    没太看懂。
    我们这边错误码都是封装好的,每次使用都是 new 出来的,自带简略堆栈信息和代码行号。在返回上层的时候就不再处理 error 了,也没必要。第三方的错误码转掉,保证返回网关时错误码都是同一套。线上再搭配链路追踪,查问题足够了。
    不知道能不能解决你的问题。
    依稀记得官方库已经支持 error wrap 了所以不再需要 github.com/pkg/errors ,没细看过不保真。
    Kumo31
        34
    Kumo31  
       2 天前   ❤️ 3
    可以看下这个库: https://github.com/samber/oops
    qW7bo2FbzbC0
        35
    qW7bo2FbzbC0  
       2 天前
    @Ayanokouji 在同一种错误需要多次使用的情况下,我也是参考开源项目,对错误进行实例化,然后在项目内返回这个实例化的错误。至于国际化,我不太清楚,我尽量都用英文定义错误和打印日志
    Ayanokouji
        36
    Ayanokouji  
    OP
       2 天前
    @p1gd0g 官方只支持了 %w ,错误码封装的思路,需要自定义 error 。应该和 huma 的思路类似
    https://github.com/danielgtaylor/huma/blob/main/error.go
    zoharSoul
        37
    zoharSoul  
       2 天前
    很多人没写过那种复杂业务, 他理解不了这些场景
    tiedan
        38
    tiedan  
       2 天前
    只要遇到就应该打印日志
    p1gd0g
        39
    p1gd0g  
       2 天前
    @Ayanokouji #36
    哦哦谢谢补充,做全栈后好久没看 go 的信息了。

    其实我想问,你们前端是怎么处理错误码的?如果前后端用同一套错误码,那自定义一套错误码应该是比较自然的吧。
    aababc
        40
    aababc  
       2 天前
    同样的问题,我也问过,怎么说呢,感觉 golang 这边没有啥统一的做法,看了看大佬意思就是 error 是一个 interface ,你自己想咋封装都可以
    guanzhangzhang
        41
    guanzhangzhang  
       2 天前
    dwu8555
        42
    dwu8555  
       2 天前
    Panic 没什么错,Erlang 有一个思想:“Let it crash”, 就是要让程序 Crash 掉,不要隐藏错误。
    securityCoding
        43
    securityCoding  
       2 天前
    具体业务代码主动抛 err 需要前置 log.Errorf 打印出来,如果是调用方则无脑 return 即可
    dwu8555
        44
    dwu8555  
       2 天前
    aababc
        45
    aababc  
       2 天前
    @tiedan #38 @harleyliao #4 这样真的好吗,我总的感觉就是 要么返回一个 error ,要么记录一个 error ,不要两件事都干
    Ayanokouji
        46
    Ayanokouji  
    OP
       2 天前
    @dwu8555 嗯。。。这么干,饭都没得吃了
    frank000
        47
    frank000  
       2 天前
    我现在用 zap, 只要遇到 err 错误就打 log ,同时 zap.Error(err)打印,然后返给上层错误。 每层都需要打印 log 和返回错误,一般也不会很多层 。
    Ayanokouji
        48
    Ayanokouji  
    OP
       2 天前
    @aababc error 可以自定义,java 也有自定义的 excetion ,这样做的目的为了统一错误处理。
    可以参考 https://github.com/danielgtaylor/huma/blob/main/error.go

    但是吧,即使自定义 error ,如果 error 不带堆栈,仅靠 error 大概率还是无法确定错误位置,还是得靠 error + log 来解决。
    aababc
        49
    aababc  
       2 天前
    @Ayanokouji #48 怎么说呢,这就是 go 的特点吧,我们现在的做法就是每层都追加自己的信息,相当于手工造了一个堆栈
    Ayanokouji
        50
    Ayanokouji  
    OP
       2 天前
    @aababc 是的,我目前也是这么解决的 return fmt.Errorf("xxx:%w", err),其实自定义 error 和 fmt.Errorf 的区别,就是看后续是否需要针对 error 类型细化处理(一般是中间件之类的)。
    NotLongNil
        51
    NotLongNil  
       2 天前
    @Kumo31 这个好,我也在在找类似的库,收藏了。这个能完美满足楼主的需求
    nextvay
        52
    nextvay  
       2 天前
    1. 每次返回 err 都 errors.wrap 包一下
    2. 再想打印日志的地方,记录 堆栈日志 :fmt.Sprintf("%+v", err)
    windcode
        53
    windcode  
       2 天前
    我的做法是:
    1. API/方法返回的 error ,包含错误码和层层 warp 的错误信息( ErrCode 和 Message ),但是不包含错误堆栈
    2. 服务端在中间件层统一打印错误堆栈

    核心思路是对外的接口是给用户看的,透出的应该是可读性较好、经过抽象的信息,而错误堆栈是给开发者看的,排查问题用的,所以放在日志里。
    lwldcr
        54
    lwldcr  
       2 天前
    歪个楼 2024 年的最后一天,马上就是新年以及假期,你们居然在这一本正经讨论代码问题
    OMGD
        55
    OMGD  
       2 天前
    @lwldcr 哈哈
    blur1119
        56
    blur1119  
       2 天前
    最近看最佳实践这词看吐了
    dingyaguang117
        57
    dingyaguang117  
       2 天前 via iPhone
    pkg/errors 不是标准做法嘛 😂😂
    SingeeKing
        59
    SingeeKing  
       2 天前 via iPhone   ❤️ 1
    自荐一下我自己基于 pkg/errors 改的 ee 库

    以下摘自我的博客:

    最优的方案实际上是全局使用第三方库。这里推荐使用我自己的 ee 错误处理库( https://pkg.go.dev/github.com/ImSingee/go-ex/ee ),其修改自官方的 pkg/errors 库,但基于实际需求做了一定的优化:

    1. (相比标准库)为所有的错误都包装了调用栈信息。
    2. 对于已经存在调用栈信息的,不会覆盖(来保证永远可以拿到最深层的调用栈信息)。
    3. 支持在 WithStack 时指定 skip 来使用上层栈(用于编写工具函数)。
    4. 栈信息的 StackTrace 和 Frame 可访问,以供外部工具(例如日志处理库)结构化利用。
    5. 增加 Panic 函数,调用时会自动生成 error 并记录 panic 位置信息。
    6. 所有 error 都实现了 TextMarshaler 接口,对序列化友好。
    oaix
        60
    oaix  
       2 天前
    DefoliationM
        61
    DefoliationM  
       2 天前
    promtail + loki + otel collector + prometheus + grafana 请,fmt.Error("xxx: %w",err) 已经足够了,你最后都能找到是哪里报的错。不过你既然习惯 java 的 throw try catch ,我建议还是继续使用 java 比较好,没必要强行转 go ,java 的性能不比 go 差,生态也比 go 好,完全没必要转 go 。
    BeautifulSoap
        62
    BeautifulSoap  
       2 天前 via Android
    就用 github.com/pkg/errors 这个包一层层往上套娃啊
    这个包虽然已经不维护了,但依旧是现在实际 go 项目的错误处理中的标准做法之一,你直接用就是了
    而且这个包里的代码内容简单得一批,你真遇到问题想改的话直接自己本地创建个文件,复制粘贴一份直接改就行了
    aarontian
        63
    aarontian  
       2 天前
    两年没写 go 了,我当时的做法是封装了个自己的 errorx 包和自定义的 Error 接口,模仿 throw 的做法,在里面封装好 throw 时的调用栈,以及预定义的错误码
    zjsxwc
        64
    zjsxwc  
       2 天前 via Android
    要不模拟 rust 的处理方式,

    rust 是用 Result<OK, Err>,配合问号后缀语法糖来解决的,

    所以可以首先用
    https://github.com/Boyux/go_macro
    让 go 能有类似 rust 的问号后缀语法糖,简化判断 is err 的处理,

    然后在 go 代码里模拟 Result<OK, Err>,就行了,比如

    // 定义 Result 类型,它有两个类型参数,一个表示成功的值类型,一个表示错误类型
    type Result[T any, E error] struct {
    value T
    err E
    }

    // Ok 构造函数,用于创建表示成功的 Result 实例
    func Ok[T any, E error](v T) Result[T, E] {
    return Result[T, E]{
    value: v,
    err: nil,
    }
    }

    // Err 构造函数,用于创建表示失败(有错误)的 Result 实例
    func Err[T any, E error](e E) Result[T, E] {
    return Result[T, E]{
    value: *new(T),
    err: e,
    }
    }

    // IsOk 方法判断 Result 是否是成功状态
    func (r Result[T, E]) IsOk() bool {
    return r.err == nil
    }

    // IsErr 方法判断 Result 是否是错误状态
    func (r Result[T, E]) IsErr() bool {
    return r.err!= nil
    }

    // Unwrap 方法,如果是成功状态则返回值,若是错误状态则触发 panic (类似 Rust 中直接使用.操作符获取值但不处理错误的情况)
    func (r Result[T, E]) Unwrap() T {
    if r.IsErr() {
    panic(r.err)
    }
    return r.value
    }

    // UnwrapErr 方法,如果是错误状态则返回错误,否则返回 nil
    func (r Result[T, E]) UnwrapErr() E {
    return r.err
    }


    func divide(a, b int) Result[int, error] {
    if b == 0 {
    return Err[int, error](fmt.Errorf("division by zero"))
    }
    return Ok[int, error](a / b)
    }

    func main() {
    result := divide(10, 2)
    if result.IsOk() {
    fmt.Println("Result:", result.Unwrap())
    } else {
    fmt.Println("Error:", result.UnwrapErr())
    }
    }
    henix
        65
    henix  
       2 天前   ❤️ 1
    我用了 Go 的错误处理后有个感受:调用栈真不是必需的
    说起调用栈我就想起网传的这张图: https://www.cnblogs.com/jhj117/p/5627224.html
    那么多调用栈全是中间层的,对排查错误也没啥帮助

    但题目中的这种情况属于信息过少,也无法很好排错
    那怎么办
    我认为很多时候我们需要的不是调用栈,而是错误的上下文
    比如读写文件错误的时候的文件名、请求上游 API 错误的时候的 url
    而这些都不是简单的一个调用栈能自动解决的,都需要程序员在错误发生的附近手动添加
    在错误向上传递的过程中,如果哪层有很重要的上下文,就在那一层把相关信息加到 err 里

    Error: unexpected '>' at the beginning of value 这种错误,应该把参数名和值都输出出来,并且当 err 传递到 controller 层的时候,附加上请求信息
    cooooing
        66
    cooooing  
       2 天前
    @henix 好生草的图,不过确实排查错误需要的是上下文而不是调用栈。
    iyaozhen
        67
    iyaozhen  
       2 天前
    楼主说的一点没错,这就是 go 的问题。而且业界也没达成统一(比如要不要堆栈)

    我们公司内部也很乱,基本上一个团队一个做法。楼主自己定一个就行。 目前比较推荐的做法是,自定义 error ,然后 适度包一下 fmt.Errorf("xxx:%w", err)
    调用方通过 errors.is 判断类型做业务逻辑处理

    但话说回来,go 设计上就是互联网的 c ,没有那么多特性。特别是不要用 java 的思维理解 go ,不然也是自己痛苦
    linuxsuren
        69
    linuxsuren  
       1 天前
    https://github.com/LinuxSuRen/api-testing 完全开源的接口开发、测试工具
    kivmi
        70
    kivmi  
       1 天前
    func ErrWrap(err error, message string) (e error) {
    if err != nil {
    fmt.Println(fmt.Errorf("Error: %v\nStack trace:\n%s", err, debug.Stack()))
    slog.Info(message)
    return err
    }
    return nil
    }

    func covert(data string) (result map[string]interface{}, err error) {
    e := json.Unmarshal([]byte(data), &result)
    e = ErrWrap(e, "Json 解析错误")
    return result, e
    } 类似这样的,是否满足你的需求呢?
    kivmi
        71
    kivmi  
       1 天前
    其实 github.com/gookit/slog 中已经有了所有的信息,包括行信息,当然这种情况下,对于多个链路调用没那么友好,只能看到发生错误的地方,到底是哪个模块产生的错误,还是不是很清楚,因此可以打印整个的调用栈帧,如下:

    func printCallers() {
    var pcs [10]uintptr
    n := runtime.Callers(2, pcs[:])
    frames := runtime.CallersFrames(pcs[:n])
    for {
    frame, more := frames.Next()
    fmt.Printf("Function: %s\nFile: %s\nLine: %d\n\n", frame.Function, frame.File, frame.Line)
    if !more {
    break
    }
    }
    }

    func ErrWrap(err error, message string) (e error) {
    if err != nil {
    slog.Info(message)
    printCallers()
    return err
    }
    return nil
    }

    这样既可以拿到对应的行,也可以看到整个的调用栈:

    [2025/01/01T15:45:43.828] [application] [INFO] [main.go:30,ErrWrap] Json 解析错误

    Function: main.ErrWrap
    File: F:/workspace/go/errors-demo/main.go
    Line: 31

    Function: main.covert
    File: F:/workspace/go/errors-demo/main.go
    Line: 39

    Function: main.main
    File: F:/workspace/go/errors-demo/main.go
    Line: 51

    Function: runtime.main
    File: C:/Program Files/Go/src/runtime/proc.go
    Line: 250

    Function: runtime.goexit
    File: C:/Program Files/Go/src/runtime/asm_amd64.s
    Line: 1594

    Error: invalid character '>' looking for beginning of value
    Result: map[]

    Process finished with the exit code 0
    kuanat
        72
    kuanat  
       1 天前
    关于 Go 日志的话题之前也有过几个帖子,可以参考一下,恰好我在那几个帖子里也有论述一些观点做法。

    如何更好的打印日志 https://v2ex.com/t/1043663
    kuanat
        73
    kuanat  
       1 天前
    关于 Go 日志的话题之前也有过几个帖子,可以参考一下,恰好我在那几个帖子里也有论述一些观点做法。

    如何更好的打印日志 https://v2ex.com/t/1043663
    golang 日志记录 https://v2ex.com/t/1038327
    Golang 中的 Context 为什么只有上文没有下文?一般如何传递下文? https://v2ex.com/t/1012453


    回到这个帖子的重点,关于“定位代码出错位置”这个需求,需要先明确调用栈的定义。除了代码层面的 call stack ,业务逻辑上 trace 也可以叫作调用栈。

    从 OP 的描述来看,主要矛盾是业务流程上比较长,日志中间件的报错不足以定位特定模块代码层面 call stack 的问题。

    我在上面引用的第一个帖子里提到过一些笼统的解决思路。

    帖子里我提到的 debug/release 双版本具体实现是用 build tags 做一个开关,release 版本没有任何额外输出,debug 版本会输出 code path 的相关信息。或者理解成单元测试 coverage 的做法。这样不仅可以知道当前模块的输入、输出,也知道具体代码的分支路径。

    这个做法给我节省了大量 debug 的时间,之前经常需要单步看执行逻辑,现在基本上看下分支流程就能大致定位问题了。并不是一定要通过反射或者什么方式获得出错的代码行才叫定位。
    Ayanokouji
        74
    Ayanokouji  
    OP
       1 天前
    @henix #65 认同这个观点,调用栈属于语言或者框架层面的保底机制。有了上下文也可以快速帮助排错。Error: unexpected '>' at the beginning of value 这种错误,仅仅用一个 fmt.Errorf("xxx:%w",err),也不太好处理,需要结合日志或者自定义错误类型处理
    xyqhkr
        75
    xyqhkr  
       1 天前
    要点:
    1. 只在入口处打印一次错误日志。其它地方绝对不打印错误。
    解决调用位置方法有两个:
    1. 在 return 处 使用 Wrap 包装。
    2. 在第一次 err 处 new CustomErr 结构。
    ForkNMB
        76
    ForkNMB  
       23 小时 55 分钟前
    这还需要三方库? 自己写点代码美化一下输出就好吧 用 syslog 为例,syslog.New(syslog.LOG_LOCAL0, "XXX") 包装一下常用的 Info Error Debug 方法 写出去写统一 format 一下。
    至于函数名 行号 堆栈这些,简单用 pc, file, line, _ := runtime.Caller(n) n 具体数字取决于你的封装 。堆栈可以等有 Panic 时再处理打印出来 平时定位 error 也不需要像 java 那样打印堆栈吧。遇到 error 打印,那肯定是根据实际情况有些是必须打的,有些可以合并处理在上层补充就行。
    alexliux
        77
    alexliux  
       21 小时 16 分钟前
    @henix 是的,我也一直在给团队强调要把上下文打印出来,不要干瘪瘪的只有一个错误。
    qloog
        78
    qloog  
       18 小时 48 分钟前   ❤️ 1
    使用 github.com/pkg/errors

    1.业务最底层,比如 db,api, rpc 等等,使用 errors.Wrap(...) - 携带堆栈
    2.中间层,errors.WithMessage(err, "your custom msg...") - 携带本层的自定义信息
    3.最上层打印错误日志,log.Errorf("xxxxx, err: %w", error) - 打印日志

    PS: 中间使用 errors.WithMessage 而不是 errors.Wrap ,是未了避免最上层打印太多的堆栈信息,只在最底层携带一次堆栈信息
    ikaros
        79
    ikaros  
       16 小时 57 分钟前
    以前用 logrus 的时候有个参数可以打印出代码具体位置,行信息,可以看下是怎么实现的
    maladaxia
        80
    maladaxia  
       14 小时 15 分钟前
    @zacard 正解, golang error 也可以打印栈的
    Ayanokouji
        81
    Ayanokouji  
    OP
       5 小时 55 分钟前
    @ikaros 在错误的位置打印日志的话,不要调用栈也没关系,这样相当于自定义上下文
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5774 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 06:24 · PVG 14:24 · LAX 22:24 · JFK 01:24
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.