求教: golang error 和 log 的最佳实践思路 - V2EX
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 331 天前 6530 次点击
    这是一个创建于 331 天前的主题,其中的信息可能已经有所发展或是发生改变。
    用 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  
       331 天前
    业务代码一般不打印日志, 中间件统一在入口处理,底层错误返回 error.Warp 足够的信息(callstack 等) 让中间件统一处理
    Ayanokouji
        2
    Ayanokouji  
    OP
       331 天前
    @pluswu1986 用的是 github.com/pkg/errors 这个库吗,但是这个已经不更新了
    csys
        3
    csys  
       331 天前
    没懂你这啥问题

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

    如果遇到 error 就打印,这样日志太多了吧,比如 sevice 层,查了 5 次 sql ,那就需要写打印五次日志吗。
    bli22ard
        6
    bli22ard  
       331 天前
    可能是最佳实战的做法是,你调用了标准库,或者第三方库,这些库返回 error 之后,你应该先用一些第三方 error 库 wrap 一下,主要目的是记录一下 error 发生的调用栈,这样上层什么位置拿到 error ,都能打印出来这个 error 是哪个位置发生的。还有一种,目的类似的做法,不记录调用栈,而改为附加一个错误码进去,这样上层的任何调用者也可以知道 error 哪里发生的,不过维护错误码这种方式维护时间越长,越容易搞混乱,导致排查问题困难。
    原则上,只要不是本工程的代码生成的 error 都进行一次 wrap ,然后向上 return ,如果没有最上层,则进行错误日志打印。
    Ayanokouji
        7
    Ayanokouji  
    OP
       331 天前
    @bli22ard 嗯,这种做法比较认同的。第三方 error 库不太好找,有推荐的吗 github.com/pkg/errors 这个已经停更了。
    还有为了兼容 slog ,可能还需要封装一些代码。
    Pdk5a8759cbeD6CH
        8
    Pdk5a8759cbeD6CH  
       331 天前
    @Ayanokouji 我们就是遇到 err 就打印,“查五次”就要打印五次的 err ,要写五次 if err != nil ,因为可能第一个 err 会影响第二次的查询,不判断没法往下走啊
    Ayanokouji
        9
    Ayanokouji  
    OP
       331 天前
    @dylanqqt 不能往下走是没问题的,就是五次 if err != nil 里边还有写五次打印日志,遇到错误也只能是其中一处,实际打印的日志也是一次
    csys
        10
    csys  
       331 天前   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
    Pdk5a8759cbeD6CH
        11
    Pdk5a8759cbeD6CH  
       331 天前
    @Ayanokouji 那就是在最底层的那个 err 打印吧
    Ayanokouji
        12
    Ayanokouji  
    OP
       331 天前
    @dylanqqt 嗯,就是最底层其实不是那么好界定,写法也比较嗦。不过这样的日志确实能当调用栈串联起来
    bli22ard
        13
    bli22ard  
       331 天前
    @Ayanokouji github.com/pkg/errors 这个库还能再战。其实自己定义也可以,实现 error 的 struct ,增加一个 Wrap 函数,参数接受一个 error ,函数体,runtime 拿调用栈,将结果保存在 struct 的成员变量,这样就可以用了
    qW7bo2FbzbC0
        14
    qW7bo2FbzbC0  
       331 天前
    ```go
    err := doJob()
    if err != nil {
    return fmt.Errorf("do job failed, %w", err)
    }
    ```
    Ayanokouji
        15
    Ayanokouji  
    OP
       331 天前
    @qW7bo2FbzbC0 我目前就是这种做法,这种做法的缺陷是,前缀的 message 需要足够清晰和唯一,清晰是为了可读,唯一是为了代码搜索定位当调用栈来用。
    还有如果项目做国际化,这种需要定义错误吗,维护比较费精力。
    lbp0200
        16
    lbp0200  
       331 天前
    这个和语言无关
    错误的地方记录日志,包括当前行,当前文件
    上级代码只需要知道有错误就行
    zacard
        17
    zacard  
       331 天前
    和 java 的异常处理类似,底层库/函数用 errors 包装携带堆栈,上层统一捕获 error 打印即可。github.com/pkg/errors 虽然不更新了,但是够用了
    guanzhangzhang
        18
    guanzhangzhang  
       331 天前
    两种,一种是 wrap 一下信息,我博客很多文章搜报错能直接搜到开源项目源码里去,另一种是 zap 这种 logger 能打印带文件 go:行数的
    Kauruus
        19
    Kauruus  
       331 天前
    你在 error 里带上调用栈也不是不行(例如 https://github.com/go-errors/errors ),然后在顶层处理,打印或者发到 sentry 。
    adgfr32
        20
    adgfr32  
       331 天前   1
    我一般也是每层返回的时候包一些关键字, 相当于人肉造了一个栈.
    xxlxiaxiaolei
        21
    xxlxiaxiaolei  
       331 天前
    @guanzhangzhang 真张馆长?我还有你 QQ 呢
    Linxing
        22
    Linxing  
       331 天前
    WithStack
    pkoukk
        23
    pkoukk  
       331 天前
    一般我不会携带堆栈信息,太多了,去获取当前堆栈的资源消耗也太重了。
    其实很简单啊,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  
       331 天前
    @Ayanokouji #2 golang 的 1.21 可以直接用%w 来包裹 error ,也可以用 errors.join 来合并多个 error 。不需要第三方库了。
    pkoukk
        25
    pkoukk  
       331 天前
    对你的 APPEND 的回复:
    那你直接用 panic ,那玩意里面自带堆栈,去上游 revocer 。
    喜欢 try catch 的,用 panic recover 去
    Nazz
        26
    Nazz  
       331 天前
    我的做法是用 github.com/pkg/errors 带上错误堆栈, 用 zerolog 设置错误级别, 在响应函数里面写入日志, lumberjack 做日志切分
    Ayanokouji
        27
    Ayanokouji  
    OP
       331 天前
    @pkoukk 你是二极管吗,要看调用栈就得用 panic 啊,你家写 go 全是 panic 啊。前面写那么多看了吗,还是说你写过 go 吗。有能耐把你的解决方法写出来啊。
    Vitumoc
        28
    Vitumoc  
       331 天前
    没有特别看懂 OP 想要表达什么。

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

    大概就这样:

    ```go
    import "runtime/debug"

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

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

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

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

    核心思路是对外的接口是给用户看的,透出的应该是可读性较好、经过抽象的信息,而错误堆栈是给开发者看的,排查问题用的,所以放在日志里。
    lwldcr
        54
    lwldcr  
       331 天前
    歪个楼 2024 年的最后一天,马上就是新年以及假期,你们居然在这一本正经讨论代码问题
    OMGD
        55
    OMGD  
       331 天前
    @lwldcr 哈哈
    blur1119
        56
    blur1119  
       331 天前
    最近看最佳实践这词看吐了
    dingyaguang117
        57
    dingyaguang117  
       331 天前 via iPhone
    pkg/errors 不是标准做法嘛
    SingeeKing
        59
    SingeeKing  
    PRO
       331 天前 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  
       331 天前
    DefoliationM
        61
    DefoliationM  
       331 天前
    promtail + loki + otel collector + prometheus + grafana 请,fmt.Error("xxx: %w",err) 已经足够了,你最后都能找到是哪里报的错。不过你既然习惯 java 的 throw try catch ,我建议还是继续使用 java 比较好,没必要强行转 go ,java 的性能不比 go 差,生态也比 go 好,完全没必要转 go 。
    BeautifulSoap
        62
    BeautifulSoap  
       330 天前 via Android
    就用 github.com/pkg/errors 这个包一层层往上套娃啊
    这个包虽然已经不维护了,但依旧是现在实际 go 项目的错误处理中的标准做法之一,你直接用就是了
    而且这个包里的代码内容简单得一批,你真遇到问题想改的话直接自己本地创建个文件,复制粘贴一份直接改就行了
    aarontian
        63
    aarontian  
       330 天前
    两年没写 go 了,我当时的做法是封装了个自己的 errorx 包和自定义的 Error 接口,模仿 throw 的做法,在里面封装好 throw 时的调用栈,以及预定义的错误码
    zjsxwc
        64
    zjsxwc  
       330 天前 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  
       330 天前   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  
       330 天前
    @henix 好生草的图,不过确实排查错误需要的是上下文而不是调用栈。
    iyaozhen
        67
    iyaozhen  
       330 天前
    楼主说的一点没错,这就是 go 的问题。而且业界也没达成统一(比如要不要堆栈)

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

    但话说回来,go 设计上就是互联网的 c ,没有那么多特性。特别是不要用 java 的思维理解 go ,不然也是自己痛苦
    freestyle
        68
    freestyle  
       330 天前 via iPhone
    linuxsuren
        69
    linuxsuren  
       330 天前
    https://github.com/LinuxSuRen/api-testing 完全开源的接口开发、测试工具
    kivmi
        70
    kivmi  
       330 天前
    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  
       330 天前   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  
       330 天前
    关于 Go 日志的话题之前也有过几个帖子,可以参考一下,恰好我在那几个帖子里也有论述一些观点做法。
    如何更好的打印日志 https://v2ex.com/t/1043663
    kuanat
        73
    kuanat  
       330 天前   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
       329 天前
    @henix #65 认同这个观点,调用栈属于语言或者框架层面的保底机制。有了上下文也可以快速帮助排错。Error: unexpected '>' at the beginning of value 这种错误,仅仅用一个 fmt.Errorf("xxx:%w",err),也不太好处理,需要结合日志或者自定义错误类型处理
    xyqhkr
        75
    xyqhkr  
       329 天前
    要点:
    1. 只在入口处打印一次错误日志。其它地方绝对不打印错误。
    解决调用位置方法有两个:
    1. 在 return 处 使用 Wrap 包装。
    2. 在第一次 err 处 new CustomErr 结构。
    ForkNMB
        76
    ForkNMB  
       329 天前
    这还需要三方库? 自己写点代码美化一下输出就好吧 用 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  
       329 天前
    @henix 是的,我也一直在给团队强调要把上下文打印出来,不要干瘪瘪的只有一个错误。
    qloog
        78
    qloog  
       329 天前   1
    使用 github.com/pkg/errors

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

    PS: 中间使用 errors.WithMessage 而不是 errors.Wrap ,是未了避免最上层打印太多的堆栈信息,只在最底层携带一次堆栈信息
    ikaros
        79
    ikaros  
       329 天前
    以前用 logrus 的时候有个参数可以打印出代码具体位置,行信息,可以看下是怎么实现的
    maladaxia
        80
    maladaxia  
       329 天前
    @zacard 正解, golang error 也可以打印栈的
    Ayanokouji
        81
    Ayanokouji  
    OP
       328 天前
    @ikaros 在错误的位置打印日志的话,不要调用栈也没关系,这样相当于自定义上下文
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1092 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 30ms UTC 18:09 PVG 02:09 LAX 10:09 JFK 13:09
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86