【工作记录】多依赖日志,少依赖调试

标签: 日志  java  编程习惯

0.为什么多依赖日志,少依赖调试?
       开发过程中,无论是写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)自助运维平台


        找线上问题的第一步,就是先去看“自助运维平台",能够定位到异常的,就直接凭着他发现问题进行修改了,如果不能详细地发现问题,这个时候,就该使用下面介绍的”XXX“或者自己去特定的机子上扒日志了。

      (2)“XXX”系统

     如图,红色圈圈代表在这分钟内发生了异常,点进去之后:


      便可以查看完整的异常调用链,这样排错的工作效率不高就怪了。
   
      总之,养成定时看日志的习惯,养成打好日志的习惯,就像是定时收邮件,多依赖日志,多借助工具,节省出排错的时间,放到更有意义的事情上去吧!



原文链接:加载失败,请重新获取