springboot打印接口调用日志的实例

目录
  • 概述
  • 方案思路
  • 封装HttpServletRequest请求
  • 把可重复读请求体通过过滤器往下传
  • 记录入参日志
    • 实现入参记录拦截器
    • 注册拦截器
  • 记录返参日志

概述

请求日志几乎是所有大型企业级项目的必要的模块,请求日志对于我们来说后期在项目运行上线一段时间用于排除异常、请求分流处理、限制流量等。

请求日志一般都会记录请求参数、请求地址、请求状态(Status Code)、SessionId、请求方法方式(Method)、请求时间、客户端IP地址、请求返回内容、耗时等等。如果你得系统还有其他个性化的配置,也可以完成记录。

记录请求参数时,由于servlet.getInputStream的数据只能读取一次,因此需要先把数据缓存下来,构造返回流,保证之后的Controller可以正常读取到请求体的数据。

方案思路

  • 封装HttpServletRequest请求类,改类在构造方法中将请求的输入流中的数据缓存了起来,保证之后的处理可以重复读取输入流中的数据。
  • 实现过滤器,把上步封装的请求类传下去,保证Controller可以正常读取输入流中的数据。
  • 添加拦截器,读取输入流中的数据。
  • 读取返回参数。

封装HttpServletRequest请求

package com.example.demo.intercept;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
/**
 * @author 
 * @date 封装HttpServletRequest请求
 */
public class RequestWrapper extends HttpServletRequestWrapper {
    private final String body;
    public RequestWrapper(HttpServletRequest request) {
        super(request);
        StringBuilder stringBuilder = new StringBuilder();
        BufferedReader bufferedReader = null;
        InputStream inputStream = null;
        try {
            inputStream = request.getInputStream();
            if (inputStream != null) {
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                char[] charBuffer = new char[128];
                int bytesBody = -1;
                while ((bytesBody = bufferedReader.read(charBuffer)) > 0) {
                    stringBuilder.append(charBuffer, 0, bytesBody);
                }
            } else {
                stringBuilder.append("");
            }
        } catch (IOException e) {
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        body = stringBuilder.toString();
    }
    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
        ServletInputStream servletInputStream = new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }
            @Override
            public boolean isReady() {
                return false;
            }
            @Override
            public void setReadListener(ReadListener readListener) {
            }
            @Override
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }
        };
        return servletInputStream;
    }
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }
    public String getBody() {
        return this.body;
    }
}

把可重复读请求体通过过滤器往下传

防止请求流读取一次后就没有了,之后的不管是过滤器、拦截器、处理器都是读的已经缓存好的数据,实现可重复读。

package com.example.demo.intercept;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
 * @author 
 * @date 防止请求流读取一次后就没有了
 */
@Component
@WebFilter(urlPatterns = "/**")
public class RecordChannelFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        ServletRequest request = null;
        if (servletRequest instanceof HttpServletRequest) {
            request = new RequestWrapper((HttpServletRequest) servletRequest);
        }
        if (request ==null){
            //防止流读取一次就没有了,将流传递下去
            filterChain.doFilter(servletRequest,servletResponse);
        }else {
            filterChain.doFilter(request,servletResponse);
        }
    }
    @Override
    public void destroy() {
    }
}

记录入参日志

实现入参记录拦截器

通过拦截器的方式实现用户入参记录。

package com.example.demo.intercept;
import com.alibaba.fastjson.JSONObject;
import org.bouncycastle.util.encoders.Base64;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import io.jsonwebtoken.Claims;
import javax.annotation.Resource;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * @author 
 * @date 记录用户操作记录入参
 */
@Component
public class OperationLogInterceptor implements HandlerInterceptor {
    /**
     * Jwt secert串,需要与加密token的秘钥一致
     */
    public static final String JWT_SECERT = "23142d7a9s7d66970ad07d8sa";
    /**
     * 需要记录的接口URL
     */
    private static List<String> pathList = new ArrayList<>();
    static {
        pathList.add("/mdms/model");
    }
    @Resource
    private FunctionDOMapper functionDOMapper;//菜单动能sql
    @Resource
    private UserOperationHistoryDOMapper historyDOMapper;//操作日志记录表
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String servletPath = "" + request.getServletPath();
        String method = request.getMethod();
        pathList.forEach(path -> {
            if (servletPath.contains(path)){
                Cookie[] cookies = request.getCookies();
                if (cookies != null) {
                    for (Cookie cookie : cookies) {
                        //获取token在请求中
                        if (cookie.getName().equals("_qjt_ac_")) {
                            String token = cookie.getValue();
                            /**解密token**/
                            byte[] encodeKey = Base64.decode(JWT_SECERT);
                            Claims claims = null;
                            try {
                                SecretKeySpec keySpec = new SecretKeySpec(encodeKey, 0, encodeKey.length, "AES");
                                claims = Jwts.parser().setSigningKey(keySpec).parseClaimsJws(token).getBody();
                            } catch (Exception e) {
                                return;
                            }
                            //用户账号
                            String account = claims.getSubject();
                            //查询URL在功能表中的功能
                            functionDOMapper.selectOne(servletPath, method);
                            //获取入参
                            RequestWrapper requestWrapper = null;
                            if (request instanceof HttpServletRequest) {
                                requestWrapper = new RequestWrapper(request);
                            }
                            Map<String,Object> map = new HashMap<>();
                            map.put("parameter", JSONObject.parse(requestWrapper.getBody()));
                            historyDOMapper.insert(map);//将操作信息入库
                        }
                    }
                } 
            }
        });
        return true;
    }
}

注册拦截器

package com.example.demo.config;
import com.example.demo.intercept.OperationLogInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
 * @author 
 * @date 注册拦截器
 */
public class WebConfig implements WebMvcConfigurer {
    @Bean
    public HandlerInterceptor getOperationLogInterceptor() {
        return new OperationLogInterceptor();
    }
    @Override
    public void addInterceptors(InterceptorRegistry registry){
        registry.addInterceptor(getOperationLogInterceptor()).addPathPatterns("/**").excludePathPatterns();
    }
}

记录返参日志

package com.example.demo.intercept;
import com.alibaba.fastjson.JSONObject;
import org.bouncycastle.util.encoders.Base64;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import javax.annotation.Resource;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.xml.ws.Response;
import java.util.*;
/**
 * @author 
 * @date 记录用户操作日志返参
 */
@ControllerAdvice(basePackages = "项目包")
public class GetResponseBody implements ResponseBodyAdvice<Object> {
    /**
     * Jwt secert串,需要与加密token的秘钥一致
     */
    public static final String JWT_SECERT = "23142d7a9s7d66970ad07d8sa";
    /**
     * 需要记录的接口URL
     */
    private static List<String> pathList = new ArrayList<>();
    static {
        pathList.add("/mdms/model");
    }
    @Resource
    private FunctionDOMapper functionDOMapper;//菜单动能sql
    @Resource
    private UserOperationHistoryDOMapper historyDOMapper;//操作日志记录表
    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        return false;
    }
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        String path = serverHttpRequest.getURI().getPath();
        String methodValue = serverHttpRequest.getMethodValue();
        pathList.forEach(serverPath -> {
            if (path.contains(serverPath)) {
                HashMap<String, String> cookieMap = new HashMap<>();
                HttpHeaders headers = serverHttpRequest.getHeaders();
                List<String> cookieList = headers.get("cookie");
                if (CollectionUtils.isEmpty(cookieList)) {
                    return;
                }
                String replaceAll = cookieList.get(0).replaceAll(";", "").replaceAll(";", "");
                String[] split = replaceAll.split(";");
                for (String cookie : split) {
                    String[] param = cookie.split("=");
                    cookieMap.put(param[0], param[1]);
                }
                String token = cookieMap.get("_qjt_ac_");
                byte[] encodeKey = Base64.decode(JWT_SECERT);
                Claims claims = null;
                try {
                    SecretKeySpec keySpec = new SecretKeySpec(encodeKey, 0, encodeKey.length, "AES");
                    claims = Jwts.parser().setSigningKey(keySpec).parseClaimsJws(token).getBody();
                } catch (Exception e) {
                    return;
                }
                //用户账号
                String account = claims.getSubject();
                //查询URL在功能表中的功能
                functionDOMapper.selectOne(servletPath, method);
                //获取返参
                List<Object> list = historyDOMapper.select("功能表参数", account);
                list.sort((Object1,Object2)->Object2.getTime().compareTo(Object1.getTime()));//将查询到的操作记录按时间降序排列
                Object history = list.get(0);
                if (body instanceof Response) {
                    Response response = (Response) body;
                    JSONObject jsonObject = JSONObject.parseObject(history.getParam());
                    jsonObject.put("message",response.getMessage());
                    jsonObject.put("body",response.getData());
                    history.setParam(jsonObject.toString());
                    history.setDes(response.getMessage());
                }
                historyDOMapper.updateByPrimaryKeySelective(history);//将操作信息更新
            }
        });
        return body;
    }
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • SpringBoot中使用AOP打印接口日志的方法

    前言 AOP 是 Aspect Oriented Program (面向切面)的编程的缩写.他是和面向对象编程相对的一个概念.在面向对象的编程中,我们倾向于采用封装.继承.多态等概念,将一个个的功能在对象中来实现.但是,我们在实际情况中也发现,会有另外一种需求就是一类功能在很多对象的很多方法中都有需要.例如有一些对数据库访问的方法有事务管理的需求,有很多方法中要求打印日志.按照面向对象的方式,那么这些相同的功能要在很多地方来实现或者在很多地方来调用.这就非常繁琐并且和这些和业务不相关的需求耦合太

  • Spring Boot如何通过自定义注解实现日志打印详解

    前言 在我们日常的开发过程中通过打印详细的日志信息能够帮助我们很好地去发现开发过程中可能出现的Bug,特别是在开发Controller层的接口时,我们一般会打印出Request请求参数和Response响应结果,但是如果这些打印日志的代码相对而言还是比较重复的,那么我们可以通过什么样的方式来简化日志打印的代码呢? SpringBoot 通过自定义注解实现权限检查可参考我的博客:SpringBoot 通过自定义注解实现权限检查 正文 Spring AOP Spring AOP 即面向切面,是对OO

  • SpringBoot使用AOP记录接口操作日志的方法

    目录 一.操作日志简介 1.1.系统日志和操作日志的区别 1.2.操作日志记录实现方式 二.AOP面向切面编程 2.1.AOP简介 2.2.AOP作用 2.3.AOP相关术语 2.4.JointPoint和ProceedingJoinPoint 2.5.AOP相关注解 三.AOP切面实现接口日志记录 3.1.引入AOP依赖 3.2.创建日志信息封装类WebLog 3.3.创建切面类WebLogAspect 3.4.调用接口进行测试 四.AOP切面+自定义注解实现接口日志记录 4.1.自定义日志注

  • 使用spring boot通过自定义注解打印所需日志

    spring boot自定义注解打印日志 在实际项目中可能需要监控每个接口的请求时间以及请求参数等相关信息,那么此时我们想到的就是两种实现方式,一种是通过拦截器实现,另一种则通过AOP自定义注解实现. 本文介绍自定义注解实现方式 自定义注解,四个元注解这次就不解释了. @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface WebLog { /** * 日志信息描述 */ String d

  • springboot打印接口调用日志的实例

    目录 概述 方案思路 封装HttpServletRequest请求 把可重复读请求体通过过滤器往下传 记录入参日志 实现入参记录拦截器 注册拦截器 记录返参日志 概述 请求日志几乎是所有大型企业级项目的必要的模块,请求日志对于我们来说后期在项目运行上线一段时间用于排除异常.请求分流处理.限制流量等. 请求日志一般都会记录请求参数.请求地址.请求状态(Status Code).SessionId.请求方法方式(Method).请求时间.客户端IP地址.请求返回内容.耗时等等.如果你得系统还有其他个

  • Springboot打印接口的三种方式分享

    目录 1 aop切面的方式 1.1 实现思路 1.2 代码实现 1.3 功能测试 2 过滤器的方式 3 拦截器的方式 1 aop切面的方式 1.1 实现思路 引入aop依赖 自定义注解 定义切面,采用环绕通知 1.2 代码实现 1)引入依赖 xml <!--aop--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-

  • springboot 实现接口灰度发布的实例详解

    目录 前言 最小化改造方式 springmvc接口请求原理 HandlerMapping简介 RequestCondition接口定义 代码实现过程 1.添加一个自定义注解用于标注接口类以及接口方法 2.自定义HandleMapping 3.自定义封装RequestCondition 4.注册自定义的ApiVersionHandleMapping 5.接口测试 前言 对灰度发布有所了解的同学应该知道,灰度发布的目的之一,就是能够根据业务规则的调整,交互上呈现不同的形式,举例来说,当前有2个版本,

  • SpringBoot+slf4j实现全链路调用日志跟踪的方法(一)

    SpringBoot中除了常见的分布式链路跟踪系统zipkin.skywalking等,如果需要快速定位一次请求的所有日志,那么该如何实现?实际slf4j提供了MDC(Mapped Diagnostic Contexts)功能,支持用户定义和修改日志的输出格式以及内容.本文将介绍 Tracer集成的slf4j MDC功能,方便用户在只简单修改日志配置文件的前提下输出当前 Tracer 上下文 TraceId. MDC介绍 MDC(Mapped Diagnostic Context,映射调试上下文

  • SpringBoot+slf4j线程池全链路调用日志跟踪问题及解决思路(二)

    本项目源码已在多个项目中实践 接着上一篇文章,项目中使用了线程池,那么子线程中日志就会丢失traceId,下面讲解如何实现子线程中的traceId日志跟踪. 解决思路 子线程在打印日志的过程中traceId将丢失,解决方式为重写线程池,将主线程的traceId继续传递到子线程中.当然,对于直接new创建线程的情况不考略[实际应用中应该避免这种用法]. 继承ThreadPoolExecutor,重写执行任务的方法 public final class OverrideThreadPoolExecu

  • webservice实现springboot项目间接口调用与对象传递示例

    目录 一.百度百科 二.webservice的技术支持 1.XML和XSD 2.SOAP 3.WSDL 4.UDDI 5.调用RPC与消息传递 三.webservice的应用场景和弊端 1.webservice的应用场景 2.webservice的弊端 四.webservice代码实例 服务端项目代码 客户端项目代码: 一.百度百科 Web Service是一个平台独立的,低耦合的,自包含的.基于可编程的web的应用程序,可使用开放的XML(标准通用标记语言下的一个子集)标准来描述.发布.发现.

  • SpringBoot 项目使用hutool 工具进行 http 接口调用的处理方法

    目录 写作目的 hutool简单介绍 实践 引用依赖 post get 请求 end 写作目的 在实际的开发过程中一个互联网的项目来说 ,有可能会涉及到调用外部接口的实际业务场景,原生的比如使用httpclient 也能够达到自己想要的结果处理 ,但是其实在实际开发的时候如果没有使用过类似的技术处理的话或多祸首可能会遇见问题所以这里我简单记录一下今天使用到的工具类: hutool 进行接口http 请求调用处理. hutool简单介绍 关于hutool工具包其实本人使用的不多哈 ,这里面其实封装

  • Spring Boot2集成AOPLog来记录接口访问日志

    前言 日志是一个Web项目中必不可少的部分,借助它我们可以做许多事情,比如问题排查.访问统计.监控告警等.一般通过引入slf4j的一些实现框架来做日志功能,如log4j,logback,log4j2,其性能也是依次增强.在springboot中,默认使用的框架是logback. 我们经常需要在方法开头或结尾加日志记录传入参数或返回结果,以此来复现当时的请求情况.但是手动添加日志,不仅繁琐重复,也影响代码的美观简洁.本文引入一个基于AOP实现的日志框架,并通过spring-boot-starter

  • springboot整合mybatis将sql打印到日志的实例详解

    在前台请求数据的时候,sql语句一直都是打印到控制台的,有一个想法就是想让它打印到日志里,该如何做呢? 见下面的mybatis配置文件: <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-

  • Django 使用logging打印日志的实例

    Django使用python自带的logging 作为日志打印工具.简单介绍下logging. logging 是线程安全的,其主要由4部分组成: Logger 用户使用的直接接口,将日志传递给Handler Handler 控制日志输出到哪里,console,file- 一个logger可以有多个Handler Filter 控制哪些日志可以从logger流向Handler Formatter 控制日志的格式 用户使用logging.getLogger([name])获取logger实例. 如

随机推荐