生產環境產生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); } }
本文旨在總結和分享,如有不對的地方,請你們幫忙提點問題所在並提個建議,望共同成長。