Request log 记录出入请求详细信息

背景

对出入站的请求进行统一的日志打印以及存储。

入站请求(应用于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());
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

W如Q扬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值