golang 日志记录

50 天前
 nobject

对于golang大家一般是怎么做错误日志记录的。

如果我的代码层级是controller->service->repo层,在repo层的错误一直往上抛,抛到最外层, 我的想法是用中间件errorMiddleware统一处理error并记录错误日志.

但有以下几个问题:

  1. 使用zap内置的错误zap.Error(err), err的堆栈信息如果原始err不记录,会直接丢失。
  2. 使用pkg/errors记录stack或者fmt.Errorf去记录调用/逻辑栈,但如果我在错误处记录一些现场数据,用起来相比zap.Field难用, 比如是个map结构体,里面还带指针什么,用%#v之类的也不能设置进去。
  3. 直接在errorlogger记录, 那每一层的err都会记录,导致重复logger

不知道大家有什么好的方式去处理

1647 次点击
所在节点    Go 编程语言
19 条回复
dreampuf
50 天前
1. 不受控的 err 无法强制要求 traceback
2. https://github.com/davecgh/go-spew
3. 只在最外层 logger ,中间函数均使用 `fmt.Errorf`

相比成本高昂的 traceback ,我通常选择一个唯一的 ttl
hailaz
50 天前
me1onsoda
50 天前
怎么这么 Java 味
kuanat
50 天前
我不确定我的做法是不是 idiomatic 只是简单描述一下我的思路。毕竟 slog 进标准库也没多久,看 api 也是受 zap 影响很大。

大的逻辑方面我比较倾向于全局只有一个唯一 logger ,中间调用过程只传递。另外我习惯把业务层面的日志和功能实现层面的日志分开,然后两者用不同的方式来处理。

业务层面的日志我倾向于用格式化的字符串:一方面它是给运维用的,需要一眼看明白在哪个环节出了问题,但并不需要知道出问题的细节;另一方面这类日志要进日志服务器做后续统计等功能,需要方便处理。

对应的就是你的描述里 controller->service->repo 这个部分,我会用 errors 传递字符串,每个环节用 errorf 来 wrap 上一个环节的错误,起到简单的 stack trace 的功能。这里一般不建议用 runtime 来获取完整的 stack trace ,主要是性能开销的问题。

包或者实现层面的日志,一般是给开发者方便调试用的,比如看到底是什么入参触发了什么样的边界条件导致出错。这个信息在开发和测试环节比较有用,实际线上是相对冗余的信息。

日志记录这个信息我用的是自定义的 context ,因为标准库里的 context 实际上只有下文没有上文。可以看这个帖子 https://s.v2ex.com/t/1012453 我的回复。

用 context 的话可以自定义数据结构,能够比较好处理那些复杂的结构体,也就是你说的第二条里的麻烦。

对于 context 信息的记录就没有必要走业务层面的 logger 了。对于不太敏感又注重性能的业务,我的处理是加一个开关,当线上出现的问题无法复现的时候,开启开关再记录完整的 runtime stack trace 信息。对于相对敏感的业务,会单独用一个 logger 导出到日志服务器单独的表中,使用 traceId 对业务层面的日志和实现层面的日志做个关联。业务层面的日志要保留很久,但功能层面的日志一般三个月就可以清理了。
nobject
50 天前
@me1onsoda 这跟 java 与 go 好像没任何关系吧?你在业务处理的时候不记日志?既然记了日志,就会有碰到相关问题吧,函数调用链长,我的 err 的日志记在哪里合适
nobject
50 天前
@hailaz 谢谢啦,一会去看看
nobject
50 天前
@dreampuf 嗯,谢谢了,我看看第二个库满足不满足我的需求。我也觉得记录 fmt.Errorf 这种逻辑栈记录下来好像也够了
nobject
50 天前
@kuanat 非常感谢,感觉给我了很大的启发。特别是使用 ctx 的方式去记录复杂的一些数据结构,感觉是个不错的思路
zzhaolei
50 天前
不是日志的问题,而是 controller->service->repo 层 一看就是 Java 转过来的。

楼主你就说你是不是 Java 转的?纯好奇
nobject
50 天前
@zzhaolei 这和 java 有啥关系呢,即使 php 古早的框架也有 controller 与 model 层呢。为啥重点放这里,难道你们代码都不需要分层?无论是 controller ,service 还是 repo ,model ,这只是个分层的名词而已。如果你们一点也不分层当我没说咯
dilu
50 天前
我的习惯是 repo 这一层打印日志,上面零星会打印一些关键日志。
zzhaolei
49 天前
@nobject 我会分层,但是不严格按照这个规范来。会以代码的量来区分不同的层级,也不会叫 controller
yuhuai
49 天前
他们好奇楼主是 java 转过来的是因为 mvc+仓储,而 go 的框架大多是[注册路由和中间件-> 路由绑定函数 -> 函数内操作 dal 和 model]一般是这样
nobject
49 天前
@yuhuai 事实上,现在其他语言任何的成熟的框架都很少把业务逻辑层与数据访问层放在同一层或只整合成一层,这不仅仅是 java,php,c#主流的框架也一样,这是写业务代码,不是写中间件或一些基础组件啊。ps:我以前是写 php 的,并不写 java 的
nobject
49 天前
@zzhaolei 不太懂怎么按代码量来分层的,如果是按代码量,我宁愿不分层,多建个文件写业务就行了。如果我一个业务,写着写着本来只有 1000 行,我再加个接口就变 1200 行了,我就开始分层了?那这个分层过程还要做抽离呢,还是仅仅把新增的接口业务放在另一层?
zzhaolei
49 天前
@nobject 比如我有 a 、b 两个类型,如果围绕他俩的函数或其他方法的代码量很少,我就不会拆,大一点了就会拆成两个文件分别存。我是比较讨厌提前优化和过度优化的。go 本身也是不提倡严格分层的,而是提倡根据你的代码来
nobject
49 天前
@zzhaolei 对于三层架构,我并不觉得这是多过度优化,过度优化的是复杂的设计模式,依赖注入控制反转那些。三层架构在写业务代码的时候是有优势的,我觉得这无关语言。而且这也不是我想探讨的主题
zzhaolei
49 天前
@nobject 嗯,你用起来就完事儿了,反正咱俩又不一起写代码
xiaozirun
48 天前
关于记录一些数据,需要像 php 封装异常一样,去实现一些错误类型,例如:

还有一些指针想获得对应值可以使用反射

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1038327

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX