程序员进阶-日志记录的技巧

新技术层出不穷,很多人觉得抓住新技术就能抓住知识,抓住地位,最后其实什么都抓不住。工作多年后我发现那些优秀的程序员其实大多在『吃老本』,比如他们懂网络编程,懂数据库,再懂点业务在Web领域就可以混的风生水起,无论新技术迭代多快,本质还是离不开网络编程和数据库。

写日志是一件很不起眼的事,老板绝对不会因为你日志写的好给你加工资。但是如果你日志写的好,肯定能在Web领域混的风生水起,因为日志在Web领域有着举足轻重的地位,类似飞机上的黑匣子。

如果你常因为写日志而烦恼,请牢记下面这些规范。

一. 日志要有分隔符

大多时候我们使用 | 作为分隔符,格式如下:

类名|方法名|输入参数|输出参数

分隔符作为参数的边界非常重要,它决定着日志是否可用,是否好用。分析数据的时候直接用分隔符拆分就是对应的字段属性。

错误例子:

类名方法名输入参数输出参数 (不用分隔符)

类名#方法名 输入参数|输出参数 (用多总分隔符)

二. 避免重复记录

在一次请求中,同样的内容理论上只需要记录一次。比如接口传入的参数。重复记录会造成磁盘空间的浪费,不利于快速定位错误点。
错误例子:

1
2
3
4
//inputArr 不应该记录多次
Log(json_encode(inputArr)."|test error");
//一些逻辑
Log(json_encode(inputArr)."|unknow error");

inputArr 被重复记录了两次,第二次的记录完全是冗余内容,查找问题时不但没有起到作用反而会产生干扰。

三. 通过uuid和编号来保证日志的连贯性

一个请求应该有一个唯一的编号,每记录一次日志还应该有一个对应的编号。比如下面的日志记录。

1
2
api.ERROR: 79a8ea37dceff105|0|responseObj is error:{"return_code":"SUCCESS","return_msg":"OK"}
api.ERROR: 79a8ea37dceff105|1|App\Request|{"subject":"201906179ae"}

79a8ea37dceff105 是本次请求的全局uuid,0,1表示记录的顺序编号。这样能保证一次请求的所有日志都可追踪,可查看链路信息。

下面是一段PHP记录日志的代码,实现了UUID与编号自动记录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static $uuid;
public static $mid = 0;

public static function log($level, $msg, $context = [])
{
if(empty(self::$uuid)){
// 对当前微秒值做散列
self::$uuid = hash('fnv164',uniqid());
}

$msg = self::$uuid."|".self::$mid."|".$msg;
self::$mid++;
$logger->log($msg, $context);
}

记录日志时会自动添加请求编号与日志记录顺序。

四. 日志编码统一用json

在记录数组和对象时统一使用jsonEncode(),json是比较通用的格式,方便解析。不要使用print(),serialize()等扩展性不好的数据格式。

五. 日志种类区分

错误日志一定要有error关键字,否则没人知道那是错误日志。普通日志需要有info,notice等关键字。之后无论是日志归拢还是分隔,一眼就知道日志的属性。

六. 服务日志

我们提供的Web接口都属于服务,并且有规范的输入输出参数,比如输入参数为用户订单号,输出参数为订单详情。

服务的输入与输出应该统一在接口的入口和出口函数中记录,过程中不记录。千万不要在代码中使用die() 和 exit() 等强制退出函数。任何时候die() 和 exit() 都是可以通过 if return 等语法代替。

规范的做法应该像nginx,将请求的参数和返回的http code统一记录,中间除非报错否则不会产出额外日志。

服务接口执行过程中应该只记录重要的中间处理数据,比如调用了第三方接口,可以记录第三方接口的请求和返回数据。

如下:

1
2
3
//result 是第三方接口返回的数据
result = aop->execute(request);
Log(json_encode(result));

七. 自定义日志

自定义的日志一定要和上下文数据一起记录,比如想记录微信支付失败,那么一定要记录请求微信支付的相关参数与微信支付接口返回的参数。如果只记录微信支付失败这几个字是毫无意义的,必须要有上下文这几个文字信息才有意义。

PS:最优秀的做法是不要有自定义日志,如果判断到了错误信息,应该直接返回给接口调用方,由上层统一记录日志。

八. 重要日志需要脱敏

用户绑定手机号或者邮箱时,会把手机号和邮箱作为参数传到服务端,我们在记录日志时应该把用户手机号和邮箱做脱敏处理,比如中间几位用*号代替。还有密码,身份证等敏感信息更要脱敏。
日志是最容易泄露的数据,很难去保护,如果哪天大量用户的手机号等信息泄露可能就是日志未脱敏惹的祸,这个严重的大锅只能自己背。

九. 最后

大量地输出无效日志不利于系统性能提升,也不利于快速定位错误点。记录日志时请思考:

这些日志真的有人看吗?

看到这条日志你能做什么?

能不能给问题排查带来好处?

写日志的最高境界是帮助自己用最少的字符得到最有用的结论。