工作经验:Java 系统记录调用日志,并且记录错误堆栈
Posted 不忘初心
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了工作经验: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; } }
以上是关于工作经验:Java 系统记录调用日志,并且记录错误堆栈的主要内容,如果未能解决你的问题,请参考以下文章