日志打印的基本原则是:重点突出、尽量短、自然、易搜索
成功日志:动词+一些事
Created sockfd=29 to localhost:7407
Waiting for channel@localhost:7406 to start
Destroyed FiberGroup(0x159e000)
动词开头大写,更易被注意到。写前面而不是后面,可以更快地感知主题。
别忘了加上时态,以更好地区分事情前后,读起来也更自然。发生在事情之前用进行时(常为ing后缀),发生在事情之后的用过去式(常为ed后缀)。
尽量用不同的动词,grep会比较方便,否则易匹配到大量无关日志。
失败日志:Fail to 动词+一些事
Fail to plan tps, Not enough tps
Fail to wait epollfd=57982058515, The fiber has a break
"Fail to"可以让阅读者第一眼就意识到"有什么失败了",而无需扫描那些夹杂在句子中央的"failed"。区分成功和失败也非常机械化,搜索一下Fail to+动词就好了。其余规则参考成功日志。
打印多个值:name1=value1 name2=value2 name3=value3 ...
不要用冒号分隔name和value,因为冒号还有其他很多含义和使用场景(如ip和端口之间),会干扰到grep效率,而等号几乎不可能有其他含义。
日志应尽量避免多层结构,否则脚本处理很麻烦。对于少量的结构化数据打印:
value是pb message的话可以直接调用ShortDebugString()获得字段描述。
支持RTTI的语言一般都可直接生成结构体的描述。
自定义描述可参考如下方法:value是结构体的话用{}包围,空格分隔,如{time=10 tps=1000 host=192.168.0.1:8080},用逗号分隔的话会显得比较挤,看不清楚;value是数组的话用[]包围,空格分隔,比如[12 13 14 15 16] ,若要突出下数组的长度,可加上长度前缀,用冒号分隔,如 5:[12 13 14 15 16];value是pair或tuple的话用()包围,逗号分隔,比如(10,20)。
用简单英语
越复杂的单词越容易拼错。当需要处理紧急线上问题时,复杂或拼错的单词可能导致排查者找不到对应的日志,来来回回增加沟通成本。
懂得汇总
虽然每秒打印上百条日志,对程序影响较小(打印一条日志的代价大约是10us)。但如果一秒打上几万甚至几十万条,那这个服务基本就不可用了(debug时除外)。
当需要高频打印日志时,请考虑:
能否用统计量代替,如个数、均值、方差、异常值等。可以的话应考虑bvar或prometheus。
这些值是否有明显的共性,可以把一批数据汇总为一行?如一个session的所有日志可以缓存在thread-local或session-local中直至session结束时再一次打印出去。
如果你真的需要每个值,且这些值难以归并(极其少见),应该把它们打印到专门的文件中去并进行相应的IO优化,而不是打印在普通程序日志中。