springboot打印接口調(diào)用日志的實(shí)例
概述
請(qǐng)求日志幾乎是所有大型企業(yè)級(jí)項(xiàng)目的必要的模塊,請(qǐng)求日志對(duì)于我們來(lái)說(shuō)后期在項(xiàng)目運(yùn)行上線一段時(shí)間用于排除異常、請(qǐng)求分流處理、限制流量等。
請(qǐng)求日志一般都會(huì)記錄請(qǐng)求參數(shù)、請(qǐng)求地址、請(qǐng)求狀態(tài)(Status Code)、SessionId、請(qǐng)求方法方式(Method)、請(qǐng)求時(shí)間、客戶端IP地址、請(qǐng)求返回內(nèi)容、耗時(shí)等等。如果你得系統(tǒng)還有其他個(gè)性化的配置,也可以完成記錄。
記錄請(qǐng)求參數(shù)時(shí),由于servlet.getInputStream的數(shù)據(jù)只能讀取一次,因此需要先把數(shù)據(jù)緩存下來(lái),構(gòu)造返回流,保證之后的Controller可以正常讀取到請(qǐng)求體的數(shù)據(jù)。
方案思路
- 封裝HttpServletRequest請(qǐng)求類,改類在構(gòu)造方法中將請(qǐng)求的輸入流中的數(shù)據(jù)緩存了起來(lái),保證之后的處理可以重復(fù)讀取輸入流中的數(shù)據(jù)。
- 實(shí)現(xiàn)過(guò)濾器,把上步封裝的請(qǐng)求類傳下去,保證Controller可以正常讀取輸入流中的數(shù)據(jù)。
- 添加攔截器,讀取輸入流中的數(shù)據(jù)。
- 讀取返回參數(shù)。
封裝HttpServletRequest請(qǐng)求
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請(qǐng)求
?*/
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;
? ? }
}把可重復(fù)讀請(qǐng)求體通過(guò)過(guò)濾器往下傳
防止請(qǐng)求流讀取一次后就沒(méi)有了,之后的不管是過(guò)濾器、攔截器、處理器都是讀的已經(jīng)緩存好的數(shù)據(jù),實(shí)現(xiàn)可重復(fù)讀。
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 防止請(qǐng)求流讀取一次后就沒(méi)有了
?*/
@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){
? ? ? ? ? ? //防止流讀取一次就沒(méi)有了,將流傳遞下去
? ? ? ? ? ? filterChain.doFilter(servletRequest,servletResponse);
? ? ? ? }else {
? ? ? ? ? ? filterChain.doFilter(request,servletResponse);
? ? ? ? }
? ? }
? ? @Override
? ? public void destroy() {
? ? }
}記錄入?yún)⑷罩?/h2>
實(shí)現(xiàn)入?yún)⒂涗洈r截器
通過(guò)攔截器的方式實(shí)現(xiàn)用戶入?yún)⒂涗洝?/p>
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 記錄用戶操作記錄入?yún)?
?*/
@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;//菜單動(dòng)能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在請(qǐng)求中
? ? ? ? ? ? ? ? ? ? ? ? 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;
? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? ? ? //用戶賬號(hào)
? ? ? ? ? ? ? ? ? ? ? ? ? ? String account = claims.getSubject();
? ? ? ? ? ? ? ? ? ? ? ? ? ? //查詢URL在功能表中的功能
? ? ? ? ? ? ? ? ? ? ? ? ? ? functionDOMapper.selectOne(servletPath, method);
? ? ? ? ? ? ? ? ? ? ? ? ? ? //獲取入?yún)?
? ? ? ? ? ? ? ? ? ? ? ? ? ? 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);//將操作信息入庫(kù)
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }?
? ? ? ? ? ? }
? ? ? ? });
? ? ? ? return true;
? ? }
}注冊(cè)攔截器
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 注冊(cè)攔截器
?*/
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 = "項(xiàng)目包")
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;//菜單動(dòng)能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;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? //用戶賬號(hào)
? ? ? ? ? ? ? ? String account = claims.getSubject();
? ? ? ? ? ? ? ? //查詢URL在功能表中的功能
? ? ? ? ? ? ? ? functionDOMapper.selectOne(servletPath, method);
? ? ? ? ? ? ? ? //獲取返參
? ? ? ? ? ? ? ? List<Object> list = historyDOMapper.select("功能表參數(shù)", account);
? ? ? ? ? ? ? ? list.sort((Object1,Object2)->Object2.getTime().compareTo(Object1.getTime()));//將查詢到的操作記錄按時(shí)間降序排列
? ? ? ? ? ? ? ? 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;
? ? }
}以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- springboot整合mybatis將sql打印到日志的實(shí)例詳解
- springboot配置aop切面日志打印過(guò)程解析
- SpringBoot AOP處理請(qǐng)求日志打印功能代碼實(shí)例
- springboot+mybatis配置控制臺(tái)打印sql日志的方法
- springboot下mybatis-plus如何打印sql日志和參數(shù)到日志文件
- SpringBoot項(xiàng)目實(shí)現(xiàn)日志打印SQL的常用方法(包括SQL語(yǔ)句和參數(shù))
- 在SpringBoot框架中實(shí)現(xiàn)打印響應(yīng)的日志
- SpringBoot整合MyBatis和MyBatis-Plus請(qǐng)求后不打印sql日志的問(wèn)題解決
相關(guān)文章
SpringMVC結(jié)合天氣api實(shí)現(xiàn)天氣查詢
這篇文章主要為大家詳細(xì)介紹了SpringMVC結(jié)合天氣api實(shí)現(xiàn)天氣查詢,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05
Java實(shí)現(xiàn)經(jīng)典游戲打磚塊游戲的示例代碼
這篇文章主要介紹了如何利用Java實(shí)現(xiàn)經(jīng)典的游戲—打磚塊。玩家操作一根螢?zāi)簧纤降摹鞍糇印?,讓一顆不斷彈來(lái)彈去的“球”在撞擊作為過(guò)關(guān)目標(biāo)消去的“磚塊”的途中不會(huì)落到螢?zāi)坏紫隆8信d趣的小伙伴可以了解一下2022-02-02
Json 自定義使用函數(shù)的簡(jiǎn)單實(shí)例
下面小編就為大家?guī)?lái)一篇Json 自定義使用函數(shù)的簡(jiǎn)單實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-10-10
Java 反射調(diào)用靜態(tài)方法的簡(jiǎn)單實(shí)例
下面小編就為大家?guī)?lái)一篇Java 反射調(diào)用靜態(tài)方法的簡(jiǎn)單實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-06-06
淺談java中為什么實(shí)體類需要實(shí)現(xiàn)序列化
下面小編就為大家?guī)?lái)一篇淺談java中為什么實(shí)體類需要實(shí)現(xiàn)序列化。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-05-05
Java數(shù)據(jù)結(jié)構(gòu)之線段樹詳解
線段樹是一種二叉搜索樹,與區(qū)間樹相似,它將一個(gè)區(qū)間劃分成一些單元區(qū)間,每個(gè)單元區(qū)間對(duì)應(yīng)線段樹中的一個(gè)葉結(jié)點(diǎn)。本文將介紹線段樹的Java實(shí)現(xiàn)代碼,需要的可以參考一下2022-01-01

