【Go语言探险】线上奇怪日志问题的排查
最近在日志中发现一些奇怪的日志,大致长这样:
Error 2020-08-28 06:59:38.813+00:00 ... _msg=get immersion context, fetch tracks failed, error: <nil>
打印了 Error 日志,error 打印出来却是 <nil>,乍眼一看,以为又遇到了 Go 里面 nil != nil 的问题,但找到对应的那行代码是这样的:
tracks, errResult = TrackService.GetRpcTracks(httpCtx, trackIds)
if errResult != nil {
logs.CtxError(httpCtx.RpcContext(), "get immersion context, fetch tracks failed, error: %v", errResult)
return
}
errResult 的类型是 *ErrorResult ,GetRpcTracks 函数返回的类型也是*ErrorResult,经过仔细研究,排除了这种可能性。
那就很奇怪了,errResult != nil 显然要成立才会往下走,一个非 nil 的结构体指针打印出来却是 nil ???
就在挠头搔耳也找不到答案时,决定再根据日志上下文来查找答案。GetRpcTracks 函数是根据 TrackIDs 来获取Track信息,如果找不到会返回错误。根据日志流,找到该请求的全部日志,其中有一条显示
Error 2020-08-28 06:59:38.815+00:00 ... _msg=no tracks found, track ids: [676648224091215xxxx]
对应的代码是:
if len(tracks) == 0 {
logs.CtxError(httpCtx.RpcContext(), "no tracks found, track ids: %v", trackIds)
errResult = ginf_action.NewErrorResult(errors.ResourceNotFound, nil, "")
}
上数据库里查了一下对应的TrackID,发现状态确实为不可用,所以接口肯定查不出来数据,这样的话 GetRpcTracks 函数返回的就是由 ginf_action.NewErrorResult(errors.ResourceNotFound, nil, "") 所构建的结构体指针。NewErrorResult 的代码其实很简单:
func NewErrorResult(catalog ErrorCatalog, err error, message string, params ...interface{}) *ErrorResult {
return &ErrorResult{
Catalog: catalog,
Err: err,
Message: fmt.Sprintf(message, params...),
}
}
所以可以肯定,tracks, errResult = TrackService.GetRpcTracks(httpCtx, trackIds) 这里返回的 errResult 确实没什么问题,那么问题应该就出在打印日志的时候了。
于是我构建了一个新的结构体来进行了测试:
type CustomNil struct {
msg string
err error
}
func TestErr(t *testing.T) {
c := &CustomNil{
msg: "",
err: nil,
}
fmt.Printf("CustomNil:%v", c)
}
打印出来的日志为:
CustomNil:&{ <nil>}
跟之前日志里打印的很像,但是不一样,前面日志是这样的:error: <nil> 没有 &,也没有大括号。于是我跟同事讨论了一下,为什么会出现这样的情况,同事说可能是String方法导致的。于是我给 CustomNil 加了一个方法:
func (c *CustomNil) String() string {
return "test"
}
重新跑一下发现日志变成了这样:
CustomNil:test
显然,使用 %v 占位符时会调用 String() 方法,所以有可能是 *ErrorResult 的 String() 方法里返回了 nil。但很快发现 *ErrorResult 根本没有实现 String() 方法,但是实现了 Error() 方法,便想会不会是这家伙导致的,于是继续进行实验,再添加一个方法:
func (c *CustomNil) Error() string {
return fmt.Sprint(c.err)
}
重新跑代码之后,日志长这样:
CustomNil:<nil>
这下终于找到原因了,%v 占位符会优先调用 Error() 方法来打印日志,没有 Error() 方法时会调用 String() 方法来打印,这两个函数都没有的情况下,会将结构体内的各个变量顺序打印。
那么问题来了,为什么是这样的呢?于是继续往下翻代码:
func (l *Logger) CtxError(ctx context.Context, format string, v ...interface{}) {
if LevelError < l.CtxLevel(ctx) {
return
}
if len(v) == 0 {
l.fmtLog(ctx, LevelError, "", format)
return
}
l.fmtLog(ctx, LevelError, "", fmt.Sprintf(format, v...))
}
CtxError 方法里调用了 fmt.Sprintf(format, v...) 来处理参数,fmt 包的 Sprintf 就很复杂了,经过一番研究,终于找到了关键代码:
func (p *pp) handleMethods(verb rune) (handled bool) {
if p.erroring {
return
}
...
// If we're doing Go syntax and the argument knows how to supply it, take care of it now.
if p.fmt.sharpV {
...
} else {
// If a string is acceptable according to the format, see if
// the value satisfies one of the string-valued interfaces.
// Println etc. set verb to %v, which is "stringable".
switch verb {
case 'v', 's', 'x', 'X', 'q':
// Is it an error or Stringer?
// The duplication in the bodies is necessary:
// setting handled and deferring catchPanic
// must happen before calling the method.
switch v := p.arg.(type) {
case error:
handled = true
defer p.catchPanic(p.arg, verb, "Error")
p.fmtString(v.Error(), verb)
return
case Stringer:
handled = true
defer p.catchPanic(p.arg, verb, "String")
p.fmtString(v.String(), verb)
return
}
}
}
return false
}
看到这里,就豁然开朗了,如果使用了 %v 占位符,会依次判断它是否实现了 error 接口和 Stinger 接口并调用 Error() 或 String() 方法来进行输出。
到此,问题就已经研究清楚了,所以使用 fmt 包来进行日志格式化时还是要注意这一点,否则就会出现一些奇奇怪怪的日志,增加不必要的麻烦。当然,在这个 case 下,这样的情况打 Error 等级的日志是否合适也是值得商讨的。
这次问题排查没有花太多时间,但整个过程就像解密一样酣畅淋漓,感觉十分有趣,最后还能从中学到一些新东西,可谓收获颇丰。特此记录下来,希望能与君共勉。
【Go语言探险】线上奇怪日志问题的排查的更多相关文章
- Linux(2)---记录一次线上服务 CPU 100%的排查过程
Linux(2)---记录一次线上服务 CPU 100%的排查过程 当时产生CPU飙升接近100%的原因是因为项目中的websocket时时断开又重连导致CPU飙升接近100% .如何排查的呢 是通过 ...
- 一次线上CPU高的问题排查实践
一次线上CPU高的问题排查实践 前言 近期某一天上班一开电脑,就收到了运维警报,有两台服务CPU负载很高,同时收到一线同事反馈 系统访问速度非常慢,几乎无响应. 一个美好的早晨,最怕什么就来什么.只好 ...
- 线上CPU飙升100%问题排查
本文转载自线上CPU飙升100%问题排查 引子 对于互联网公司,线上CPU飙升的问题很常见(例如某个活动开始,流量突然飙升时),按照本文的步骤排查,基本1分钟即可搞定!特此整理排查方法一篇,供大家参考 ...
- 线上Redis偶发性链接失败排查记
问题过程 输入法业务于12月12日上线了词库接受业务,对部分用户根据用户uuid判断进行回传,在12月17日早上8点多开始出现大量的php报错(Redis went away),报错导致了大量的链接积 ...
- 一次性搞清楚线上CPU100%,频繁FullGC排查套路
“ 处理过线上问题的同学基本上都会遇到系统突然运行缓慢,CPU 100%,以及 Full GC 次数过多的问题. 当然,这些问题最终导致的直观现象就是系统运行缓慢,并且有大量的报警. 本文主要针对系统 ...
- 一次线上Redis类转换异常排查引发的思考
之前同事反馈说线上遇到Redis反序列化异常问题,异常如下: XxxClass1 cannot be cast to XxxClass2 已知信息如下: 该异常不是必现的,偶尔才会出现: 出现该异常后 ...
- 原创 记录一次线上Mysql慢查询问题排查过程
背景 前段时间收到运维反馈,线上Mysql数据库凌晨时候出现慢查询的报警,并把原始sql发了过来: --去除了业务含义的sql update test_user set a=1 where id=1; ...
- 一则线上MySql连接异常的排查过程
Mysql作为一个常用数据库,在互联网系统应用很多.有些故障是其自身的bug,有些则不是,这里以前段时间遇到的问题举例. 问题 当时遇到的症状是这样的,我们的应用在线上测试环境,JMeter测试过程中 ...
- 线上CPU飙升100%问题排查,一篇足矣
一.引子 对于互联网公司,线上CPU飙升的问题很常见(例如某个活动开始,流量突然飙升时),按照本文的步骤排查,基本1分钟即可搞定!特此整理排查方法一篇,供大家参考讨论提高. 二.问题复现 线上系统突然 ...
随机推荐
- PHP zip_entry_compressedsize() 函数
定义和用法 zip_entry_compressedsize() 函数返回 zip 档案项目的压缩文件尺寸.高佣联盟 www.cgewang.com 语法 zip_entry_compressedsi ...
- PHP crc32() 函数
实例 输出 crc32() 的结果:高佣联盟 www.cgewang.com <?php $str = crc32("Hello World!"); printf(" ...
- UOJ 422 [集训队作业2018] 小Z的礼物 min-max容斥 期望 轮廓线dp
LINK:小Z的礼物 太精髓了 我重学了一遍min-max容斥 重写了一遍按位或才写这道题的. 还是期望多少时间可以全部集齐. 相当于求出 \(E(max(S))\)表示最后一个出现的期望时间. 根据 ...
- luogu P4525 自适应辛普森法1
LINK:自适应辛普森法1 观察题目 这个东西 凭借我们的数学知识应该是化简不了的. 可以直接认为是一个函数 求定积分直接使用辛普森就行辣. 一种写法: double a,b,c,d; double ...
- 7月13日考试 题解(DFS序+期望+线段树优化建图)
T1 sign 题目大意:给出一棵 N 个节点的树,求所有起点为叶节点的有向路径,其 上每一条边权值和的和.N<=10000 水题.考试的时候毒瘤出题人(学长orz)把读入顺序改了一下,于是很多 ...
- spring的IOC(反转控制)
Spring概念 1.1.1 spring 是什么 Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:反转控制 ...
- 用var声明变量,在java中居然有这么多细节,你都了解么?
简介 Java SE 10引入了局部变量的类型推断.早先,所有的局部变量声明都要在左侧声明明确类型. 使用类型推断,一些显式类型可以替换为具有初始化值的局部变量保留类型var,这种作为局部变量类型 的 ...
- Python3.x+Fiddler抓取APP数据
随着移动互联网的市场份额逐步扩大,手机APP已经占据我们的生活,以往的数据分析都借助于爬虫爬取网页数据进行分析,但是新兴的产品有的只有APP,并没有网页端这对于想要提取数据的我们就遇到了些问题,本章以 ...
- 用Python绘制一套“会跳舞”的动态图形给你看看
在读技术博客的过程中,我们会发现那些能够把知识.成果讲透的博主很多都会做动态图表.他们的图是怎么做的?难度大吗?这篇文章就介绍了 Python 中一种简单的动态图表制作方法. 看这优美的舞姿 很多人学 ...
- C++运用栈实现网络浏览器的“前进”与“后退”功能
在用户最近访问的网页中进行“前进”和“后退”是Web浏览器的常用功能,实现该功能的一种方式是使用两个栈(backward 栈和forward栈)来存储用户访问的网址,用户的不同操作对应的具体实现方法如 ...