背景
对出入站的请求进行统一的日志打印以及存储。
入站请求(应用于controller以及webservice)
使用aspect切面,只要使用@annotation(com.zg.common.annotation.Log)标注的方法都会进入此切面。
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.task.TaskExecutor;
import org.springframework.http.HttpMethod;
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.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
* 处理入站请求的切面
*/
@Slf4j
@Aspect
@Component
public class RequestLogAspect {
@Resource
IRequestInfoService requestInfoService;
@Resource
private TaskExecutor logExecutor;
private final String SYSTEM = "";
/**
* execution 指定controller
* !execution 剔除指定controller下的方法
*/
@Pointcut("@annotation(com.zg.common.annotation.Log)")
public void requestLogAspect() {
}
@Around("requestLogAspect()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
// 获得注解
Log ctrlLog = getAnnotationLog(joinPoint);
ApiOperation apiOperation = getAnnotationApiOperation(joinPoint);
Object ret = null;
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
HttpServletResponse response = attributes.getResponse();
Map<String, String> paramMap = new HashMap<>();
String methodName = null;
String remoteIP = null;
LocalDateTime startTime = LocalDateTime.now(ZoneId.systemDefault());
String exceptionMessage = null;
Map<String, String> selectedHeaderMap = new HashMap<>();
try {
// 接收到请求,记录请求内容
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 记录下请求内容
log.info("请求类型 : {} ,请求URL : {}", request.getMethod(), request.getRequestURL());
remoteIP = request.getRemoteAddr();
log.info("请求IP : {}", remoteIP);
methodName = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
log.debug("请求方法 : {}", methodName);
//从HttpServletRequest中获取headers
Enumeration<String> en = request.getHeaderNames();
Map<String, String> headerMap = new HashMap<>();
while (en.hasMoreElements()) {
String headerName = en.nextElement();
String headerValue = request.getHeader(headerName);
headerMap.put(headerName, headerValue);
}
//打印所有header
//header太长,导致log可读性差,因此改为debug级别
log.debug("请求headers : {}", JacksonUtil.writeValue(headerMap));
//只存储下面三个header
selectedHeaderMap.put("x-userid-header", request.getHeader("x-userid-header"));
selectedHeaderMap.put("x-user-header", request.getHeader("x-user-header"));
selectedHeaderMap.put("x-tenant-header", request.getHeader("x-tenant-header"));
// 记录post方法 传json格式的数据
if (HttpMethod.POST.name().equals(request.getMethod())) {
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
String[] paramNames = u.getParameterNames(method);
//方法 1 请求的方法参数值 JSON 格式 null不显示
if (joinPoint.getArgs().length > 0) {
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
//请求参数类型判断过滤,防止JSON转换报错
if (args[i] instanceof HttpServletRequest || args[i] instanceof HttpServletResponse || args[i] instanceof MultipartFile) {
continue;
}
paramMap.put(paramNames[i], JacksonUtil.writeValue(args[i]));
log.info("请求参数 : {}, 内容 : {}", paramNames[i], JacksonUtil.writeValue(args[i]));
}
}
} else {
//请求的方法参数值 兼容fromDate格式和JSON格式
Object[] args = joinPoint.getArgs();
// 请求的方法参数名称 显示所有字段 值为null也显示该字段
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
String[] paramNames = u.getParameterNames(method);
if (args != null && paramNames != null) {
StringBuilder params = new StringBuilder();
for (int i = 0; i < args.length; i++) {
//请求参数类型判断过滤,
if (args[i] instanceof HttpServletRequest || args[i] instanceof HttpServletResponse || args[i] instanceof MultipartFile) {
continue;
}
paramMap.put(paramNames[i], JacksonUtil.writeValue(args[i]));
params.append(" ").append(paramNames[i]).append(": ").append(args[i]).append(",");
}
log.info("请求参数 : {}", params);
}
}
ret = joinPoint.proceed();
// 处理完请求,返回内容
log.debug("返回内容 : {}", JacksonUtil.writeValue(ret));
} catch (Exception e) {
log.error(e.getMessage(), e);
exceptionMessage = e.getMessage();
throw e;
} finally {
LocalDateTime endTime = LocalDateTime.now(ZoneId.systemDefault());
saveRequest(ret, startTime, request, paramMap, endTime, methodName, remoteIP,
response.getStatus(), selectedHeaderMap, exceptionMessage);
}
return ret;
}
private ApiOperation getAnnotationApiOperation(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null) {
return method.getAnnotation(ApiOperation.class);
}
return null;
}
/**
* 存储请求信息
*/
private void saveRequest(Object ret, LocalDateTime startTime,
HttpServletRequest request, Map<String, String> paramMap,
LocalDateTime endTime, String methodName, String remoteIP, int status,
Map<String, String> selectedHeaderMap, String exceptionMessage) {
//save request&response
logExecutor.execute(() -> {
RequestInfo requestInfo = new RequestInfo();
try {
requestInfo.setRequestType(RequestType.INBOUND.name());
requestInfo.setRequestMethod(request.getMethod());
requestInfo.setRequestBody(paramMap.toString());
requestInfo.setResponseBody(JacksonUtil.writeValue(ret));
requestInfo.setRequestTime(startTime);
requestInfo.setResponseTime(endTime);
requestInfo.setMethodName(methodName);
requestInfo.setRequestAddr(remoteIP);
requestInfo.setStatusCode(String.valueOf(status));
requestInfo.setRequestHeaders(JacksonUtil.writeValue(selectedHeaderMap));
requestInfo.setToSystem(SYSTEM);
requestInfo.setExceptionMessage(exceptionMessage);
StringBuffer url = request.getRequestURL();
if (url != null) requestInfo.setRequestUrl(url.toString());
} finally {
requestInfoService.add(requestInfo);
}
});
}
}
在controller中使用
@Slf4j
@RestController
@RequestMapping("/hello")
@Api(tags = "hello")
public class HelloController {
@GetMapping
@Log()
public Result<?> hello() {
LocalDateTime now = LocalDateTime.now();
return Result.succeed(MapUtil.builder()
.put("Long", 16889999888801L)
.put("LocalDateTime", now)
.put("TimeString", now.toString())
.build());
}
}
在webservice中使用
@Component
@WebService(name = WebService.NAME,
targetNamespace = WebService.NAMESPACE,
endpointInterface = "com.zg.webservice.WebService")
@Slf4j
public class WebServiceImpl implements WebService{
@Override
@Log(title = "hello")
public void hello(Req arg0) {
}
}
出站请求(应用于RestTemplate)
使用ClientHttpRequestInterceptor拦截器拦截http请求。
/**
* 拦截出站请求
*
*/
@Slf4j
@Component
public class OutboundRequestInterceptor implements ClientHttpRequestInterceptor {
@Resource
IRequestInfoService requestInfoService;
@Resource
private TaskExecutor logExecutor;
private final String SYSTEM = "";
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
RequestInfo requestInfo = new RequestInfo();
ClientHttpResponse response;
try {
LocalDateTime startTime = LocalDateTime.now(ZoneId.systemDefault());
log.debug("request start time: {}", startTime);
traceRequest(request, body);
response = execution.execute(request, body);
LocalDateTime endTime = LocalDateTime.now(ZoneId.systemDefault());
String responseBody = traceResponse(response);
//save request&response
requestInfo.setRequestType(RequestType.OUTBOUND.name());
HttpMethod method = request.getMethod();
if (method != null) requestInfo.setRequestMethod(method.toString());
List<String> list = request.getHeaders().get(Const.TO_SYSTEM);
if (list != null && CollUtil.isNotEmpty(list)) requestInfo.setToSystem(list.get(0));
requestInfo.setRequestHeaders(request.getHeaders().toString());
requestInfo.setRequestBody(new String(body, StandardCharsets.UTF_8));
requestInfo.setRequestUrl(request.getURI().toString());
requestInfo.setStatusCode(response.getStatusCode().toString());
requestInfo.setResponseHeaders(response.getHeaders().toString());
requestInfo.setResponseBody(responseBody);
requestInfo.setRequestTime(startTime);
requestInfo.setResponseTime(endTime);
requestInfo.setFromSystem(SYSTEM);
} catch (Exception e) {
log.error(e.getMessage(), e);
requestInfo.setExceptionMessage(e.getMessage());
throw e;
} finally {
logExecutor.execute(() -> requestInfoService.add(requestInfo));
}
log.debug("request end time: {}", LocalDateTime.now(ZoneId.systemDefault()));
return response;
}
private void traceRequest(HttpRequest request, byte[] body) throws IOException {
log.debug("Request Start (rest template) : =============================================================");
log.info("URI : {}", request.getURI());
log.debug("Method : {}", request.getMethod());
log.debug("Headers : {}", request.getHeaders());
log.info("Request body: {}", new String(body, StandardCharsets.UTF_8));
log.debug("Request End : ===============================================================================");
}
private String traceResponse(ClientHttpResponse response) throws IOException {
String body = IoUtil.readUtf8(response.getBody());
log.debug("Response Start (rest template) : =============================================================");
log.debug("Status code : {}", response.getStatusCode());
log.debug("Headers : {}", response.getHeaders());
log.info("Response body: {}", body);
log.debug("Response End : ===============================================================================");
return body;
}
}
配置拦截器
@Configuration
public class RestTemplateConfig {
@Resource
OutboundRequestInterceptor outboundRequestInterceptor ;
@Resource
RestTemplateResponseErrorHandler restTemplateResponseErrorHandler;
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder){
RestTemplate restTemplate = builder.build();
BufferingClientHttpRequestFactory bufferingClientHttpRequestFactory =
new BufferingClientHttpRequestFactory(getClientHttpRequestFactory());
restTemplate.setRequestFactory(bufferingClientHttpRequestFactory);
//设置拦截器
restTemplate.setInterceptors(Collections.singletonList(outboundRequestInterceptor));
//设置error handler
restTemplate.setErrorHandler(restTemplateResponseErrorHandler);
return restTemplate;
}
/**
* 使用HttpClient作为底层客户端
*
* @return
*/
@ConfigurationProperties(prefix = "zg.rest.connection")
private HttpComponentsClientHttpRequestFactory getClientHttpRequestFactory() {
return new HttpComponentsClientHttpRequestFactory();
}
}
ResponseErrorHandler实现
/**
* 请求返回错误处理
*/
@Slf4j
@Component
public class RestTemplateResponseErrorHandler implements ResponseErrorHandler {
@Override
public boolean hasError(ClientHttpResponse httpResponse) throws IOException {
return (httpResponse.getStatusCode().series() == HttpStatus.Series.CLIENT_ERROR
|| httpResponse.getStatusCode().series() == HttpStatus.Series.SERVER_ERROR);
}
@Override
public void handleError(ClientHttpResponse httpResponse) throws IOException{
if (httpResponse.getStatusCode().series() == HttpStatus.Series.SERVER_ERROR) {
log.error("server error: " + httpResponse.getStatusCode().value());
log.error("response: " + JacksonUtil.writeValue(httpResponse.getBody()));
throw new HttpServerErrorException(httpResponse.getStatusCode());
} else if (httpResponse.getStatusCode().series() == HttpStatus.Series.CLIENT_ERROR) {
log.error("client error: " + httpResponse.getStatusCode().value());
log.error("response: " + JacksonUtil.writeValue(httpResponse.getBody()));
throw new HttpClientErrorException(httpResponse.getStatusCode());
}
}
}