• 工作经验:Java 系统记录调用日志,并且记录错误堆栈


    前言:现在有一个系统,主要是为了给其他系统提供数据查询接口的,这个系统上线不会轻易更新,更不会跟随业务系统的更新而更新(这也是有一个数据查询接口系统的原因,解耦)。这时,这个系统就需要有一定的方便的线上查错方式,我便想到了记录每一次的调用日志,而且需要记录错误堆栈,同时被白名单过滤的也要记录下来。

    想法

      这个日志记录,需要在每一次访问接口时记录一下,在有异常时将异常的堆栈信息记录在每次访问记录里。这里由于要使用数据库信息,所以选择了 spring 的拦截器

      在拦截器抛放心之后,运行业务代码,如果抛异常(包括自定义异常),应该在抛异常之后,记录错误信息到堆栈,这时需要知道在拦截器时插入数据库的那条记录的 id,拿到这个id就可以直接更新数据,将堆栈记录。这里通过 ThreadLocal 线程本地变量来记录每一次访问插入数据库后返回的主键 id。

      而每一次的异常都需要做统一异常处理,在统一异常处理这里访问数据库,记录错误信息。

      白名单被过滤的也要记录下来,这个利用抛自定义业务异常,然后使用统一异常类来处理就好。

    实现

       接口调用日志需要一张表来记录,字段如下:

    create table t_interface_log
    (
      id             number not null,
      interface_name varchar2(100),
      caller_ip      varchar2(100),
      local_ip       varchar2(100),
      caller_params  varchar2(1000),
      caller_date    date,
      msg            varchar2(4000),
      status         varchar2(1)
    )
    ;
    -- Add comments to the table
    comment on table t_interface_log
      is '接口调用日志记录表';
    -- Add comments to the columns
    comment on column t_interface_log.id
      is '主键id';
    comment on column t_interface_log.interface_name
      is '接口名';
    comment on column t_interface_log.caller_ip
      is '调用者ip';
    comment on column t_interface_log.local_ip
      is '本机ip';
    comment on column t_interface_log.caller_params
      is '调用参数';
    comment on column t_interface_log.caller_date
      is '调用时间';
    comment on column t_interface_log.msg
      is '信息记录';
    comment on column t_interface_log.status
      is '状态:0:失败,1:成功';

      配置如下:

    <bean id="interfaceLogInterceptor" class="com.yule.common.interceptor.InterfaceLogInterceptor" />
        <mvc:interceptors>
            <mvc:interceptor>
                <mvc:mapping path="/interface/**"/>
                <ref bean="interfaceLogInterceptor" />
            </mvc:interceptor>
        </mvc:interceptors>

      Java 代码如下:

    线程变量

    package com.yule.manage.interfacelog.entity;
    
    /**
     * 接口调用日志线程变量
     * @author yule
     */
    public class InterfaceLogHolder {
    
        /**
         * 本地线程变量,用于控制每一次新增日志后返回的id
         */
        private static final ThreadLocal<String> ID_STRING_THREAD_LOCAL = new ThreadLocal<>();
    
        /**
         * 获取本地线程变量的id
         * @return id
         */
        public static String getIdStringThreadLocalValue() {
            return ID_STRING_THREAD_LOCAL.get();
        }
    
        /**
         * 设置本地线程变量的id
         * @param value id
         */
        public static void setIdStringThreadLocalValue(String value) {
            ID_STRING_THREAD_LOCAL.set(value);
        }
    
        /**
         * 移除当前线程的当前本地线程变量
         */
        public static void removeStringThreadLocal() {
            ID_STRING_THREAD_LOCAL.remove();
        }
    
    }

    拦截器

    package com.yule.common.interceptor;
    
    import com.ch.common.util.CommonTool;
    import com.yule.manage.interfacelog.entity.InterfaceLog;
    import com.yule.manage.interfacelog.entity.InterfaceLogHolder;
    import com.yule.manage.interfacelog.service.InterfaceLogService;
    import org.apache.commons.lang3.StringUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.util.Map;
    
    /**
     * 日志拦截器:记录调用日志
     * @author yule
     */
    public class InterfaceLogInterceptor extends HandlerInterceptorAdapter {
    
        @Autowired
        private InterfaceLogService interfaceLogService;
    
        private final Logger logger = LoggerFactory.getLogger(InterfaceLogInterceptor.class);
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
                throws Exception {
            try{
    
                InterfaceLog interfaceLog = new InterfaceLog();
                interfaceLog.setStatus(InterfaceLog.STATUS_SUCCESS);
    
                //方法返回发出请求的客户机的IP地址
                interfaceLog.setCallerIp(request.getRemoteAddr());
                interfaceLog.setInterfaceName(request.getRequestURI());//
                interfaceLog.setLocalIp(request.getLocalAddr());// 方法返回WEB服务器的IP地址。
    
                //返回一个包含请求消息中的所有参数名的Enumeration对象。通过遍历这个Enumeration对象,就可以获取请求消息中所有的参数名。
                Map<String, String[]> paramsMap =  request.getParameterMap();
                if(CommonTool.isNotNullOrBlock(paramsMap)){
                    StringBuilder stringBuilder = new StringBuilder();
                    for(Map.Entry<String, String[]> entry : paramsMap.entrySet()){
                        stringBuilder.append(entry.getKey()).append(": ").append(StringUtils.join(entry.getValue())).append("; ");
                    }
                    interfaceLog.setCallerParams(stringBuilder.toString());
                }
    
                this.interfaceLogService.insert(interfaceLog);
    
                //线程变量存值
                InterfaceLogHolder.setIdStringThreadLocalValue(interfaceLog.getId());
    
            } catch (Exception e) {
                logger.error("接口调用记录错误信息出错;调用者ip:" + request.getRemoteHost() + ", 调用者ip:" + request.getRemoteAddr() + ", 接口名:" + request.getRequestURI(), e);
            }
    
            return true;
        }
    }

    统一异常处理

    package com.yule.common.dealexception;
    
    import com.yule.common.entity.ResponseBase;
    import com.yule.manage.interfacelog.entity.InterfaceLog;
    import com.yule.manage.interfacelog.entity.InterfaceLogHolder;
    import com.yule.manage.interfacelog.service.InterfaceLogService;
    import com.yule.interfacepackage.pibdata.web.ctrl.PibDataCtrl;
    import org.apache.commons.lang3.exception.ExceptionUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import javax.servlet.http.HttpServletRequest;
    
    /**
     * 接口 统一异常处理,并记录错误日志
     * @author yule
     */
    @ControllerAdvice("com.yule.interfacepackage")
    public class DealInterfaceException {
        @Autowired
        private InterfaceLogService interfaceLogService;
    
        private Logger logger = LoggerFactory.getLogger(DealInterfaceException .class);
    
        @ExceptionHandler
        @ResponseBody
        public ResponseBase dealException(HttpServletRequest request, Exception ex) {
            //异常处理
            logger.error(ex.getMessage(), ex);
            ResponseBase responseBase = new ResponseBase();
            responseBase.setErrorMsg(ex.getMessage());
            responseBase.setSuccess(false);
    
            this.interfaceLogService.update(ExceptionUtils.getStackTrace(ex), InterfaceLog.STATUS_ERROR, InterfaceLogHolder.getIdStringThreadLocalValue());
    
            return responseBase;
        }
    }
  • 相关阅读:
    概率期望小记
    洛谷P5591 小猪佩奇学数学【单位根反演】
    EasyUI取消树节点选中
    EasyUI获取正在编辑状态行的索引
    js判断是否是大小写,数字等方法
    ElasticSearch部署问题
    全文检索ES 服务启动和关闭
    文件异步上传
    js控制时间显示格式
    SpringMVC 多视图解析器 跳转问题
  • 原文地址:https://www.cnblogs.com/yuxiaole/p/9230746.html
Copyright © 2020-2023  润新知