打印有效日志 - 快速定位bug产生的原因 - 提高生产系统维护效率

 

生产环境产生bug时,程序员通过日志定位原因。日志需要打印bug发生点的  入参、出参、调用堆栈信息,才能为bug分析提供有效信息。

这里使用java为例,分五点来说明如何打印有效日志。

在异常打印的分析中,我把MVC中的ctroller层定义为系统的边界。

在系统的边界使用try{}catch{},统一处理返回值,即使发生异常,也能使返回值保持固定格式。

在系统边界以内使用throws Exception 向上层传递异常信息。

在bug发生点打印入参和出参信息,并生成堆栈调用信息。堆栈信息在此处不打印,交由边界打印,避免重复打印。

小技巧,系统异常和业务逻辑异常,不使用if else判断来中断程序执行,而使用抛出异常的形式中断程序执行,有助于降低逻辑复杂度。

异常定义:

系统异常,如空指针、数组越界、非法除数等。

业务逻辑异常,如必填入参不足、余额不足时发起支付等。

系统对于异常的处理:

产生异常时,终止系统运行,返回页面并提示用户能理解的信息,系统记录详细日志。

不能把出错详情返回给用户有二点原因:

用户看不懂。

可能泄露表名和字段名,被攻击者利用。

本文的系统边界的定义

即使程序处理异常,系统边界以外成员接收到的信息必须是固定格式,所以系统边界必须有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);
    }
}

 

本文旨在总结和分享,若有不对的地方,请大家帮忙提点问题所在并提个建议,望共同成长。

  

 

转载于:https://www.cnblogs.com/youfeng-chuiguo/p/9223047.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值