摘要
最近新接手的項目經常要查問題,但是,前面一撥人,日志打的非常亂,好多就根本沒有打日志,所以弄一個AOP統一打印一下 請求數據和響應數據
框架
spring+springmvc+jersey
正文
這個項目有點老啦,竟然還有前端頁面用jsp寫的,哎,說起來都是淚。下面說說我做這個項目踩得坑吧
統一打印請求的請求數據和響應數據(接口是restful 風格,用jersey框架實現的),肯定第一反應想到老羅的Spring aop,一開始木有仔細研究框架,以為會一招spring就可以吃遍天下啦。o(╥﹏╥)o,所以花了半個小時,寫了一個spring aop,調試了1天,就是沒看見spring 給目標類生成代理。其中我懷疑過,打的切點不對,spring配置文件不對,等等,反正就木有考慮過spring 本身的問題。后來我才發現要做代理的目標package 根本就木有交給spring 托管,他是由jersey直接托管(以前也木有玩過jersey框架,看了web.xml配置以后才茅塞頓開)
下面的代碼是spring+jersey框架下日志aop (注:沒法用spring aop ,因為restful風格的package 根本就木有交給spring托管)
import com.alibaba.fastjson.JSON;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.Provider;
import java.io.*;
import java.net.URI;
import java.util.List;
/**
* Created by huxuhong on 2019/11/27.
*/
@Provider
public class OperationLogFilter implements ContainerRequestFilter,ContainerResponseFilter{
Logger logger = LoggerFactory.getLogger(OperationLogFilter.class);
@Override
public void filter(ContainerRequestContext containerRequestContext) throws IOException {
if(containerRequestContext!=null){
URI uri = null;
String path = null;
UriInfo uriInfo = containerRequestContext.getUriInfo();
if(uriInfo != null){
uri = uriInfo.getAbsolutePath();
}
if(uri != null){
path = uri.getPath();
}
String method = containerRequestContext.getMethod();
String params = inputStreamToString(containerRequestContext);
printReqInfo(path,method,params);
}else{
logger.info("請求request不存在");
}
}
@Override
public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) throws IOException {
if(containerResponseContext ==null){
logger.info("響應response不存在");
}else{
String response = outputStreamToString(containerResponseContext);
printResInfo(response);
}
}
private String inputStreamToString(ContainerRequestContext containerRequestContext ) {
StringBuffer stringBuffer = new StringBuffer();
ByteArrayOutputStream baos = null;
InputStream repeatStreamRead = null;
InputStream repeatStreamWrite = null;
try{
InputStream in = containerRequestContext.getEntityStream();
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) > -1 ) {
baos.write(buffer, 0, len);
}
baos.flush();
repeatStreamRead = new ByteArrayInputStream(baos.toByteArray());
List<String> paramsList = IOUtils.readLines(repeatStreamRead,"UTF-8");
if(paramsList!=null && !paramsList.isEmpty()){
for(String str : paramsList){
stringBuffer.append(str);
}
}
repeatStreamWrite = new ByteArrayInputStream(baos.toByteArray());
containerRequestContext.setEntityStream(repeatStreamWrite);
}catch (Throwable e){
logger.warn("解析輸入流失敗{}",e);
}finally {
try {
if(baos != null){
baos.close();
}
if(repeatStreamRead != null){
repeatStreamRead.close();
}
if(repeatStreamWrite != null){
repeatStreamWrite.close();
}
} catch (Throwable e) {
logger.warn("關閉流失敗{}",e);
}
}
return stringBuffer.toString();
}
private String outputStreamToString(ContainerResponseContext containerResponseContext ) throws IOException {
String responseStr = null;
try{
Object obj = containerResponseContext.getEntity();
if(obj != null){
responseStr = JSON.toJSON(obj).toString();
}
}catch (Throwable e){
logger.warn("解析響應數據異常{}",e);
}
return responseStr;
}
public void printReqInfo(String url,String method,String params){
logger.info("請求地址:{},請求方式:{},請求參數:{}",url,method,params);
}
public void printResInfo(String response){
logger.info("響應數據:{}",response);
}
}
下面是采用spring 的aop日志 實現方式
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.container.ContainerRequestContext;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
/**
* Created by huxuhong on 2019/11/26.
*/
@Component
@Aspect
public class OperationLogAspect {
Logger logger = LoggerFactory.getLogger(OperationLogAspect.class);
ThreadLocal<Long> startTime = new ThreadLocal<Long>();
/**
* 定義攔截規則:攔截com.ppdai.wechat.spring.controller包下面的所有類
* execution(<修飾符模式>?<返回類型模式><方法名模式>(<參數模式>)<異常模式>?)
*/
@Pointcut(value = "execution(* com.ppdai.wechat.spring.controller..*(..))")
public void serviceMethodPointcut() {
}
@Before(value = "serviceMethodPointcut()")
public void doBefore(JoinPoint joinPoint){
String url = null;
String method = null;
String param = null;
String reqConcreteClass = null;
try {
startTime.set(System.currentTimeMillis());
// 接收到請求,記錄請求內容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if(attributes == null){
return;
}
HttpServletRequest request = attributes.getRequest();
url = request.getRequestURL().toString();
method = request.getMethod();
if(method.toUpperCase().equals("GET")){
param = request.getQueryString();
}else{
for(Object obj :joinPoint.getArgs()){
if(obj instanceof MultipartFile
|| obj instanceof HttpServletRequest
|| obj instanceof HttpServletResponse){
continue;
}
param = JSON.toJSON(obj).toString();
}
}
reqConcreteClass = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
}catch (Throwable e){
logger.warn("解析請求信息異常",e);
}
printReqInfo(url,method,reqConcreteClass,param);
}
@AfterReturning(returning = "ret", pointcut = "serviceMethodPointcut()")
public void doAfterReturning(Object ret) throws Throwable {
String responseStr = null;
try {
if(ret != null){
responseStr = JSON.toJSON(ret).toString();
}
Long times = System.currentTimeMillis() - startTime.get();
printResponseInfo(responseStr,times);
}catch (Throwable e){
logger.warn("解析響應內容異常",e);
}
}
private void printResponseInfo(String response,long times){
logger.info("響應內容: {},響應耗時:{} " , response,times );
}
private void printReqInfo(String url,String method,String concreteClass,String param){
logger.info("請求URL: {},類路徑:{}, 請求方式: {}, 請求參數: {}", url,concreteClass, method, param);
}
}
望后來的人不要跟我犯同樣的錯誤,都是先入為主的觀念害人
參考資料
ssm (spring springmvc mybatis) maven 項目集成 Jersey2 入門指南
https://blog.csdn.net/gianttj/article/details/86144582
https://www.jianshu.com/p/9e135faa3efa
Jersey實現對方法進行過濾攔截
https://blog.csdn.net/qq_28334711/article/details/72925495