开发过程中,无论是写RPC接口,还是正常的业务代码,之前我的习惯是代码跑通后,进入断点调试模式,用不同的入参条件跟代码,模拟正常和非常情况,检查是否有隐藏的bug,这种做法也确实奏效,能够及时地发现一些编码时没有考虑周全的校验、以及逻辑上的一些疏忽。但是往往随着测试一步步升级,从测试2环境切到测试3,从测试3切到预生产,debug已经不能为我所用了,往往这个时候代码报出一些奇怪错误的时候,就很难下手快速定位问题,当然系统上线之后出现紧急的bug,如果提前日志没有做好“埋点”,对于开发,那简直就是“灾难的时刻”。所以,作为一名业务组的开发人员,尤其是写复杂业务接口的时候,请特别重视“日志”,这个东西很像买保险,不能给你带来什么能预见的好处,但是能在你最困难的时候,帮你一把。
1.日志分类
日志分类,本文中不要理解为究竟是“Log4J”还是“SL4J”等这个层次的分类(小编公司用的是SL4J,基于这个层次的对比,网上很多),我要说的分类,是最终打出来的日志,从功能以及使用习惯角度上的分类,暂且分为如下四类:
(1)系统致命错误日志
(2)系统可控错误日志
(3)用户操作日志
(4)系统运行日志
有开发经验的朋友很容易明白这4类日志的区别,对于日常开发,“4.系统运行日志”我们无需关注,这块收集会有架构同事在运维平台做处理,“3.用户操作日志”除非做行为分析等大数据搜集分析,无需过多关注,对于复杂业务来讲,必要处埋点,日志级别可为Error或Info,由日志收集系统规则制定,但是对于“1.系统致命错误日志”以及“2.系统可控错误日志”,作为一名后台开发就必须要着重注意了,这2中错误是必要收集的,级别最好定位Error,到线上之后将会非常方便排查问题、发现隐患。
2.使用习惯
1.养成看日志的习惯,从我在神州的一位同事身上我养成了每天看日志的习惯,尤其是重大上线之后,非常便于排除隐患。
2.代码开发测试完成之后不要急着提交,先跑一遍看看日志是否看得懂。因为日志都是打出来供大家一起看的,觉得可以了,再提交代码。
3.对于“系统致命错误日志”以及“系统可控错误日志”,在try catch的时候,logger的写法,最好这样写:
private Logger logger = LoggerFactory.getLogger(ProductBackwardNewRemoteService.class);
try{
//todo business
}catch (BusinessException e){
result.setStatus(-1);
result.setMsg("XXXXXXXXX" + e);
return result;
//这一层的日志,由上层throws的地方来打
}catch (Exception e){
logger.error("XXXXXXX",e);
result.setStatus(-1);
result.setMsg("XXXXXXXXX" + e);
return result;
}
如上, 对于catch住的异常,在logger打印的时候,最好,不要用e.getMessage(),直接用e,或者e.getStackTrace,也不知道为什么,如图:
对于RunTime异常,使用e.getMessage()经常打印出来的信息是Null。(有知道的,可以分享给我哦~)
4.对于能够避免的异常,就不要抓啦,举个例子 :
Integer serviceFeeTermRepayNum = (calcParamDTO.getServiceFeeTermRepayNum() !=null && calcParamDTO.getServiceFeeTermRepayNum() != 0) ?
calcParamDTO.getServiceFeeTermRepayNum(): 1;
Double ServiceFeeAmt = calcParamDTO.getServiceFeeAmt() !=null ? calcParamDTO.getServiceFeeAmt():0D;
//每期还款金额 = 服务费金额 / 还款期数
Double serviceFeeTermRepayAmt = AmtCalcBaseUtil.get2Double(ServiceFeeAmt / serviceFeeTermRepayNum);
这里可能会抛出“计算异常”,但是能够避免,就用三元表达式避免就ok了,可以让程序继续执行就Ok了。
5.在异常处理模块中提供适量的错误原因信息。比如,如果封装了RPC的结果类型Result,定义公共错误Code码:
public class RemoteCommConstant {
/**
* 远程服务成功
*/
public final static int REMOTE_SUCCES_STATUS = 0;
/**
* 远程服务失败
*/
public final static int REMOTE_ERROR_STATUS = -1;
public enum RemoteCommonResult {
SUCCESS("成功",0),
ERROR_IN_SERVER("服务器内部错误", -500),
PARAMETER_EMPTY("参数值不能为空", -400),
UN_LOGIN("未登录,当前请求需登陆后访问", -910),
LOGIN_ERROR("登录异常,请稍候重试", -912),
CALL_OFTEN("请求频繁", -920),
REMOTE_INSTAN_EXCEPTION("获取远程服务对象实例化异常", -921),
REMOTE_ILLEGALACCESS__EXCEPTION("非法访问异常", -922),
//and so on ……
RemoteCommonResult(String name, int value) {
this.name = name;
this.value = value;
}
private String name;
private int value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public int getIndex() {
return value;
}
}
}
6.将try/catch区段置于循环之外。
7.不要将异常用于程序流程控制条件。
8.线上系统日志,最好打印出机器ip或者名称,如果线上某系统部署在6台不同的机器上,比如下图:
此时,如果想要深入去查找问题,通过打印出来的机器名称,直接xshell到那台机器,通过时间找到上下文立刻查看问题。至于怎么sl4j配置日志打印机器名称或ip,在nginx上有配置,location下添加add_header,添加机器名称,从而让返回头里面返回是哪个机器处理的。
9.复杂业务地方,比如RPC接口入参、调用别人RPC接口处,我一般都会打上logger,非常方便复现问题。
10.Logger和RPC测试工具/单元测试结合使用,基本上==Degub本地代码效果,做到程序上线了,只要有参数,依然可以分析出数据整套流程在哪里出错。
3.日志平台
上面主要是说如何自己通过xshell连接linux服务器,到日志路径下查看定位,如果公司有成熟的日志收集系统,那一定要利用起来,那我公司举例:
(1)自助运维平台
(2)“XXX”系统
如图,红色圈圈代表在这分钟内发生了异常,点进去之后:
总之,养成定时看日志的习惯,养成打好日志的习惯,就像是定时收邮件,多依赖日志,多借助工具,节省出排错的时间,放到更有意义的事情上去吧!