手摸手教你打日志

362 阅读3分钟

为啥要记日志

撸代码的时候,都会记一下日志,方便以后排查问题。一些有经验的程序员甚至可以直接用日志代替调试。开发的时候可能忙着写业务代码,日志都来不及打,上线之后出错的时候才想起来打日志。怎么打日志能做到快速定位问题呢?

需要考虑的问题

日志的内容

日志是有重要程度的,有些东西在数据库里能找到,并且一直保持不变,没必要在日志里再输出一遍,直接查数据库就行。在代码执行后会改变的数据最需要日志。最近遇到用户反馈,一篇文章的封面图看着和文章没有关系,像是和另外一篇文章互换了。因为文章是允许多人编辑的,没办法确定是有人改动还是程序的错误,但是当时没有日志,只能新加,编辑封面图的正好是单独的接口,直接加上日志代码

log.info("旧封面图:{},新封面图:{},操作人:{}",oldPic,newPic,operatorId);

逻辑的上下文

slf4j的日志里有个变量是 %thread,表示是哪个线程输出的日志。多个线程同时运行时,上一行和下一行的输出可能分别属于不同的线程,这两行日志是不相关的。一次程序的方法调用会有调用栈的概念,一次请求会经过多个方法,这个东西就叫它调用链。每次请求都分配一个系统唯一的请求ID,就可以方便得把同一次请求的日志过滤出来。 以订单系统为例,一笔交易的流程大概是创建订单->锁定库存->支付->支付结果(成功、取消)。出现BUG的时候,如果不确定是哪个环节出了问题,可以这样加一下日志

//创建订单后
log.info("单号:[{}],订单创建成功,订单信息:[{}]",orderNo,orderEntity);
//锁定库存
log.info("单号:[{}],库存锁定成功,锁定库存数量:[{}],订单信息:[{}]",orderNo,orderStockNum,orderEntity);
//创建支付交易后
log.info("单号:[{}],支付流水号:[{}],支付流水创建成功,金额:[{}],订单信息:[{}]",orderNo,payNo,totalAmount,orderEntity);

通过日志快速定位问题

按照上面的方法打印日志,可以用linux的tailcatgrep等命令组合使用来查找出错相关的日志。在微服务项目里,一个服务会放多个节点,一次请求调用的多个服务在不同的节点上,想用traceId串联起来还是有点难度。一般会用ELK统一收集+检索日志。

一种易犯的错误写法

如果在日志里发现了异常,大概率是个BUG。但是,出现异常的时候,不一定就会有异常日志。比如catch到日志之后,直接ex.printStackTrace();打出来,这个方法的效果约等于System.err.println(),不会被slf4j记录。 另外一种是直接log.error("npe:{}",ex.getMessage());,日志可以正常打印,但是没有异常的堆栈信息,没办法快速定位,这种日志对问题排查作用不大。

本文使用 mdnice 排版