打印有效日誌 - 快速定位bug產生的緣由 - 提升生產系統維護效率

 

生產環境產生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);
    }
}

 

本文旨在總結和分享,如有不對的地方,請你們幫忙提點問題所在並提個建議,望共同成長。

相關文章
相關標籤/搜索