生产环境产生bug时,程序员经过日志定位缘由。日志须要打印bug发生点的 入参、出参、调用堆栈信息,才能为bug分析提供有效信息。java
这里使用java为例,分五点来讲明如何打印有效日志。程序员
在异常打印的分析中,我把MVC中的ctroller层定义为系统的边界。apache
在系统的边界使用try{}catch{},统一处理返回值,即便发生异常,也能使返回值保持固定格式。json
在系统边界之内使用throws Exception 向上层传递异常信息。数组
在bug发生点打印入参和出参信息,并生成堆栈调用信息。堆栈信息在此处不打印,交由边界打印,避免重复打印。app
小技巧,系统异常和业务逻辑异常,不使用if else判断来中断程序执行,而使用抛出异常的形式中断程序执行,有助于下降逻辑复杂度。工具
异常定义:测试
系统异常,如空指针、数组越界、非法除数等。ui
业务逻辑异常,如必填入参不足、余额不足时发起支付等。this
系统对于异常的处理:
产生异常时,终止系统运行,返回页面并提示用户能理解的信息,系统记录详细日志。
不能把出错详情返回给用户有二点缘由:
用户看不懂。
可能泄露表名和字段名,被攻击者利用。
本文的系统边界的定义
即便程序处理异常,系统边界之外成员接收到的信息必须是固定格式,因此系统边界必须有try{}catch{}对返回值进行处理。service层和dao层建议不写try{}catch{},而是使用throws Exception 向上层抛出异常。
如何生成堆栈信息
/** * 验证必填入参 * @param payBean * @return * @throws Exception */ public Boolean validateInParamter(PayBean payBean) throws Exception{ if(payBean.getId() == null || payBean.getName() == null){ String tipMsg = "必填参数为空"; //返回页面的提示信息 String errorMsg = tipMsg + FormatUtil.formatInParater(payBean); //记录日志的错误详情 logger.error(errorMsg); //打印入参 throw new Exception(tipMsg); //生成调用堆栈,但不打印 } return true; }
日志打印信息
2018-06-26 11:01:53 053 [main] ERROR - com.demo.exception.PayService.validateInParamter(35) | 必填参数为空入参:{"id":"","name":"zhangsan","payMoney":5000,"yzm":""}
打印堆栈信息
PayCtroller.java /** * 支付提交 * @return */ public String paySubmit(PayBean payBean){ Map<String,Object> result = new HashMap<String,Object>(); try { String id = payService.payAdd(payBean); result.put("isSuccess","true"); result.put("msg","成功"); result.put("id","id"); } catch (Exception e) { logger.error(LogUtil.getMsg(e));//打印堆栈调用 result.put("isSuccess","true"); result.put("msg",e.getMessage());//提示信息 result.put("id","id"); } logger.info("页面提示信息:"+result.get("msg").toString()); String resultJson = JSONObject.fromObject(result).toString(); return resultJson; } LogUtil.java /** * 把原始的异常信息转成字符串,返回字符串 * @param e * @return */ public static String getMsg(Exception e){ StringWriter sw = new StringWriter(); e.printStackTrace( new PrintWriter(sw, true)); String str = sw.toString(); return str; }
日志打印信息
2018-06-26 11:07:38 038 [main] ERROR - com.demo.exception.PayService.validatePayMoney(52) | 支付金额[5000.0]不能大于余额[1000.0]入参:{"id":"2","name":"zhangsan","payMoney":5000,"yzm":""}
2018-06-26 11:07:38 038 [main] ERROR - com.demo.exception.PayCtroller.paySubmit(52) | java.lang.Exception: 支付金额[5000.0]不能大于余额[1000.0] at com.demo.exception.PayService.validatePayMoney(PayService.java:53) at com.demo.exception.PayService.payAdd(PayService.java:19) at com.demo.exception.PayCtroller.paySubmit(PayCtroller.java:47) at com.com.demo.exception.TestPayCtroller.testPaySubmit(TestPayCtroller.java:23) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ...... 2018-06-26 11:07:38 038 [main] INFO - com.demo.exception.PayCtroller.paySubmit(57) | 页面提示信息:支付金额[5000.0]不能大于余额[1000.0]
完整事例,共7个类。业务类4个,工具类2个,测试类1个:
PayBean.java
package com.demo.exception; /** * Created by yqj on 2018/6/26. */ public class PayBean { private String id; private String name; private Double payMoney; private String yzm; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Double getPayMoney() { return payMoney; } public void setPayMoney(Double payMoney) { this.payMoney = payMoney; } public String getYzm() { return yzm; } public void setYzm(String yzm) { this.yzm = yzm; } }
PayCtroller.java
package com.demo.exception; import com.log.LogUtil; import net.sf.json.JSONObject; import org.apache.log4j.Logger; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.HashMap; import java.util.Map; /** * Created by yqj on 2018/6/26. */ public class PayCtroller extends HttpServlet{ private static final Logger logger = Logger.getLogger(PayCtroller.class); private PayService payService; private PayBean payBean; public PayCtroller(PayService payService) { this.payService = payService; } protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { PayBean payBean = new PayBean(); //从request中取得属性,设置PayBean //do... //调用支付 String resultJson = this.paySubmit(payBean); req.setAttribute("result",resultJson); //返回页面 //do... } /** * 支付提交 * @return */ public String paySubmit(PayBean payBean){ Map<String,Object> result = new HashMap<String,Object>(); try { String id = payService.payAdd(payBean); result.put("isSuccess","true"); result.put("msg","成功"); result.put("id","id"); } catch (Exception e) { logger.error(LogUtil.getMsg(e));//打印堆栈调用 result.put("isSuccess","true"); result.put("msg",e.getMessage());//提示信息 result.put("id","id"); } logger.info("页面提示信息:"+result.get("msg").toString()); String resultJson = JSONObject.fromObject(result).toString(); return resultJson; } public PayService getPayService() { return payService; } public void setPayService(PayService payService) { this.payService = payService; } public PayBean getPayBean() { return payBean; } public void setPayBean(PayBean payBean) { this.payBean = payBean; } }
PayDao.java
package com.demo.exception; import org.apache.log4j.Logger; /** * Created by yqj on 2018/6/26. */ public class PayDao { private static final Logger logger = Logger.getLogger(PayDao.class); public String add(PayBean payBean) throws Exception{ String id = ""; validateInParamter(payBean); //执行插入数据 return id; } /** * 验证必填入参 * @param payBean * @return * @throws Exception */ public Boolean validateInParamter(PayBean payBean) throws Exception{ if(payBean.getId() == null || payBean.getName() == null){ String errorMsg = "入参[id="+payBean.getId()+"][name="+payBean.getName()+"]不能为空"; logger.error(errorMsg); //打印入参 throw new Exception(errorMsg); //生成调用堆栈,但不打印 } return true; } }
PayService.java
package com.demo.exception; import com.util.FormatUtil; import org.apache.log4j.Logger; /** * Created by yqj on 2018/6/26. */ public class PayService { private static final Logger logger = Logger.getLogger(PayService.class); private PayDao payDao; public PayService(PayDao payDao) { this.payDao = payDao; } public String payAdd(PayBean payBean) throws Exception{ validateInParamter(payBean); validatePayMoney(payBean); validateSMSVCode(payBean); String id = payDao.add(payBean); return id; } /** * 验证必填入参 * @param payBean * @return * @throws Exception */ public Boolean validateInParamter(PayBean payBean) throws Exception{ if(payBean.getId() == null || payBean.getName() == null){ String tipMsg = "必填参数为空"; //返回页面的提示信息 String errorMsg = tipMsg + FormatUtil.formatInParater(payBean); //记录日志的错误详情 logger.error(errorMsg); //打印入参 throw new Exception(tipMsg); //生成调用堆栈,但不打印 } return true; } /** * 验证余额是否足够 * @param payBean * @return * @throws Exception */ public Boolean validatePayMoney(PayBean payBean) throws Exception{ Double yue = 1000d; if(payBean.getPayMoney() > yue){ String tipMsg = "支付金额["+payBean.getPayMoney()+"]不能大于余额["+yue+"]"; //返回页面的提示信息 String errorMsg = tipMsg + FormatUtil.formatInParater(payBean); //记录日志的错误详情 logger.error(errorMsg); //打印入参 throw new Exception(tipMsg); //生成调用堆栈,但不打印 } return true; } /** * 验证短信验证码 * @param payBean * @return * @throws Exception */ public Boolean validateSMSVCode(PayBean payBean) throws Exception{ String systemYzm = "111222"; if(!systemYzm.equals(payBean)){ String tipMsg = "手机验证码不正确"; //返回页面的提示信息 String errorMsg = tipMsg + FormatUtil.formatInParater(payBean); //记录日志的错误详情 logger.error(errorMsg); //打印入参 throw new Exception(tipMsg); //生成调用堆栈,但不打印 } return true; } }
TestPayCtroller.java
package com.com.demo.exception; import com.demo.exception.PayBean; import com.demo.exception.PayCtroller; import com.demo.exception.PayDao; import com.demo.exception.PayService; import org.junit.Test; /** * Created by yqj on 2018/6/26. */ public class TestPayCtroller { @Test public void testPaySubmit(){ PayBean payBean = new PayBean(); payBean.setId("2"); payBean.setName("zhangsan"); payBean.setPayMoney(5000d); PayDao payDao = new PayDao(); PayService payService = new PayService(payDao); PayCtroller payCtroller = new PayCtroller(payService); payCtroller.paySubmit(payBean); } }
LogUtil.java
package com.log; import java.io.PrintWriter; import java.io.StringWriter; /** * Created by yqj on 2018/6/10. */ public abstract class LogUtil { /** * 把原始的异常信息转成字符串,返回字符串 * @param e * @return */ public static String getMsg(Exception e){ StringWriter sw = new StringWriter(); e.printStackTrace( new PrintWriter(sw, true)); String str = sw.toString(); return str; } public static void main(String[] args){ /*PayAppayBean payAppayBean = new PayAppayBean(); String str = FormatUtil.formatInParater(payAppayBean); System.out.println(str); str = FormatUtil.formatOutParamter(null); System.out.println(str); List<LbParameter> parameterList = new ArrayList<LbParameter>(); parameterList.add(new LbParameter("KHH", "123")); parameterList.add(new LbParameter("KHXM", "344")); parameterList.add(new LbParameter("KHQC", "456")); str = FormatUtil.formatOutParamter(parameterList); System.out.println(str);*/ } }
FormatUtil.java
package com.util; import net.sf.json.JSONArray; import net.sf.json.JSONObject; /** * 格式化工具类 * Created by yqj on 2018/6/10. */ public abstract class FormatUtil { /** * 格式化字符串 * @param info String * @param inParamterArr String * @return */ public static String formatString(String info,String... inParamterArr){ StringBuilder resultSb = new StringBuilder(info); if(inParamterArr!=null){ for (int i = 0; i < inParamterArr.length; i++) { resultSb.append("[").append(inParamterArr[i]).append("]"); } return resultSb.toString(); }else { return info; } } /** * 格式化对象为字符串 * @param inParamter Object * @return */ public static String formatObject(String info,Object inParamter){ if(inParamter!=null){ try { JSONObject o = JSONObject.fromObject(inParamter); String result = info+ o.toString(); return result; }catch (Exception e){ String result = info+ JSONArray.fromObject(inParamter).toString(); return result; } }else { return info; } } /** * 格式化入参 * @param inParamterArr String * @return */ public static String formatInParater(String... inParamterArr){ return formatString("入参",inParamterArr); } /** * 格式化入参 * @param inParamter Object * @return */ public static String formatInParater(Object inParamter){ return formatObject("入参:",inParamter); } /** * 格式化出参 * @param inParamterArr Object * @return */ public static String formatOutParamter(String... inParamterArr){ return formatObject("出参:",inParamterArr); } /** * 格式化出参 * @param resultObj Object * @return */ public static String formatOutParamter(Object resultObj){ return formatObject("出参:",resultObj); } }
本文旨在总结和分享,如有不对的地方,请你们帮忙提点问题所在并提个建议,望共同成长。