工作经验: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;
    }
}

原文地址:https://www.cnblogs.com/yuxiaole/p/9230746.html

时间: 2024-11-05 22:54:39

工作经验:Java 系统记录调用日志,并且记录错误堆栈的相关文章

Java后端程序员1年工作经验总结

java后端1年经验和技术总结(1) 1.引言 毕业已经一年有余,这一年里特别感谢技术管理人员的器重,以及同事的帮忙,学到了不少东西.这一年里走过一些弯路,也碰到一些难题,也受到过做为一名开发却经常为系统维护和发布当救火队员的苦恼.遂决定梳理一下自己所学的东西,为大家分享一下. 经过一年意识到以前也有很多认识误区,比如: 偏爱收集,经常收集各种资料视频塞满一个个硬盘,然后心满意足的看着容量不行动. 不重基础,总觉得很多基础东西不需要再看了,其实不懂的地方很多,计算机程序方面任何一个结果都必有原因

写给java web一年左右工作经验的人

摘要 大学就开始学习web,磕磕绊绊一路走过来,当中得到过开源社区很多的帮助,总结了这些年来的技术积累,回馈给开源社区. ps:图片都是从网上盗...感谢原作者. ps:文字千真万确都是我自己写的. 在此,特别感谢Hansen,他曾经有私的帮助过我(两包零食),他是一个很强的启蒙胖子. 我把我这些年在java学习中学到的东西,按照项目开发中可能遇见的场景,进行了一次梳理. 这个故事是我最后决定加上来的,我非常喜欢这个故事,软件工程中有一个被戏称为Cargo Cult编程法的编程风格,而下面这个故

一位10年Java工作经验的架构师聊Java和工作经验

从事近十年的 JavaEE 应用开发工作,现任阿里巴巴公司系统架构师.对分布式服务架构与大数据技术有深入研究,具有丰富的 B/S 架构开发经验与项目实战经验,擅长敏捷开发模式.国内开源软件推动者之一,Smart Framework 开源框架创始人.热爱技术交流,乐于分享自己的工作经验.著有<架构探险——从零开始写Java Web框架>一书. 我的十年技术之路 和大家介绍下我目前所从事的工作. 我目前从事分布式服务架构的设计与开发工作,在阿里的大数据平台上进行应用程序开发.我们整个系统架构采用了

Java(JCo3)与SAP系统相互调用

声明:原创作品,转载时请注明文章来自SAP师太技术博客:www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将追究法律责任!原文链接:http://www.cnblogs.com/jiangzhengjun/p/4291479.html 外部系统(Java)调用BAPI函数... 78 安装JCo3. 78 创建JCo3连接... 79 直连... 79 连接池... 81 DestinationDataProvider接口(不需连接属性配置文件)..

2年Java开发工作经验面试总结

最近换了个公司,从三月底开始面,面到四月底,面了有快二十家公司.我是一个喜欢总结经验的人,每经过一场面试,我在回来的路上都会仔细回想今天哪些问题可以答的更好,或者哪些问题是自己之前没遇到过的,或者是哪个知识点今天又问了等等.四月中旬的时候,我就在构思要写一篇面经,主要是想着可能对那些跟我相同处境的人有点帮助,再者就是稍微记录下这为期一个月的面试过程. 个人介绍: 首先介绍下我面试时的自身条件情况,我把自己的情况分为优势和劣势来说可能更有利于你们比较自身情况. 劣势: 1.15年7月毕业后开始到上

aop日志(记录方法调用日志)

一,使用aop记录方法调用日志 1)使用注解与aop做方法调用日志,只需要把注解添加在要记录的方法上就可以,不会影响代码结构 2)实现思路 数据库表建立>>配置需要环境>>自定义注解>>定义切点与操作(包含处理逻辑)>>添加注解 二,配置环境 1)在原来的项目pom文件中添加以下aop需要的依赖 <springframework>4.0.5.RELEASE</springframework> <aspectj>1.8.5&

一名3年工作经验的java程序员应该具备的技能

一名3年工作经验的Java程序员应该具备的技能,这可能是Java程序员们比较关心的内容.我这里要说明一下,以下列举的内容不是都要会的东西—-但是如果你掌握得越多,最终能得到的评价.拿到的薪水势必也越高. 1.基本语法 这包括static.final.transient等关键字的作用,foreach循环的原理等等.今天面试我问你static关键字有哪些作 用,如果你答出static修饰变量.修饰方法我会认为你合格,答出静态块,我会认为你不错,答出静态内部类我会认为你很好,答出静态导包我会对你很满

一名3年工作经验的java程序员应该具备的职业技能

一名3年工作经验的Java程序员应该具备的技能,这可能是Java程序员们比较关心的内容.我这里要说明一下,以下列举的内容不是都要会的东西--但是如果你掌握得越多,最终能得到的评价.拿到的薪水势必也越高. 1.基本语法 这包括static.final.transient等关键字的作用,foreach循环的原理等等.今天面试我问你static关键字有哪些作 用,如果你答出static修饰变量.修饰方法我会认为你合格,答出静态块,我会认为你不错,答出静态内部类我会认为你很好,答出静态导包我会对你很满

Java 为程序创建日志系统

使用JAVA创建日志系统有两种方法 1.使用log4j操作日志文件 2.使用系统重定向输出日志信息 方法1:使用log4j操作日志文件(可使用jar或者xml) 步骤1:下载log4j.jar 下载地址:http://mirrors.hust.edu.cn/apache/logging/log4j/1.2.17/log4j-1.2.17.zip 步骤2:导入log4j.jar 1.在当前工程处右键>new(新建)>Folder(文件夹)<没找到的话选Other>wizards>