springboot 记录访问请求的日志

最近在做请求的日志记录的功能,记录下完成的方式。
Springboot框架提供了多种方式来拦截HTTP请求和响应,只要能够获取到对应的request和response,就可以通过相应的API来获取所需要的信息。
使用Spring Boot Actuator在应用程序里提供的众多 Web 接口,了解应用程序运行时的内部状况,且能监控和度量Spring Boot 应用程序。

Spring Boot Actuator的使用

引入依赖包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

开启端口访问权限

management.endpoints.web.exposure.include=httptrace

HttpTraceRepository

InMemoryHttpTraceRepository实现了HttpTraceRepository,默认会把最近100次的HTTP请求记录到内存中

public class InMemoryHttpTraceRepository implements HttpTraceRepository {

    private int capacity = 100;

    private boolean reverse = true;

    private final List<HttpTrace> traces = new LinkedList<>();

    /**
     * Flag to say that the repository lists traces in reverse order.
     * @param reverse flag value (default true)
     */
    public void setReverse(boolean reverse) {
        synchronized (this.traces) {
            this.reverse = reverse;
        }
    }

    /**
     * Set the capacity of the in-memory repository.
     * @param capacity the capacity
     */
    public void setCapacity(int capacity) {
        synchronized (this.traces) {
            this.capacity = capacity;
        }
    }

    @Override
    public List<HttpTrace> findAll() {
        synchronized (this.traces) {
            return Collections.unmodifiableList(new ArrayList<>(this.traces));
        }
    }

    @Override
    public void add(HttpTrace trace) {
        synchronized (this.traces) {
            while (this.traces.size() >= this.capacity) {
                this.traces.remove(this.reverse ? this.capacity - 1 : 0);
            }
            if (this.reverse) {
                this.traces.add(0, trace);
            }
            else {
                this.traces.add(trace);
            }
        }
    }

}

自定义HttpTraceRepository

可以通过写自己的类来实现HttpTraceRepository,重写add方法看收集自己所需要的访问数据

@Slf4j
public class MyHttpTraceRepository implements HttpTraceRepository {

    @Override
    public List<HttpTrace> findAll() {
        return Collections.emptyList();
    }

    @Override
    public void add(HttpTrace trace) {
        String path = trace.getRequest().getUri().getPath();
        String queryPara = trace.getRequest().getUri().getQuery();
        String queryParaRaw = trace.getRequest().getUri().getRawQuery();
        String method = trace.getRequest().getMethod();
        long timeTaken = trace.getTimeTaken();
        String time = trace.getTimestamp().toString();
        log.info("path: {}, queryPara: {}, queryParaRaw: {}, timeTaken: {}, time: {}, method: {}", path, queryPara, queryParaRaw,
                timeTaken, time, method);
    }
}

然后注册到springBoot容器中

@Configuration
@ConditionalOnWebApplication
@ConditionalOnProperty(prefix = "management.trace.http", name = "enabled", matchIfMissing = true)
@EnableConfigurationProperties(HttpTraceProperties.class)
@AutoConfigureBefore(HttpTraceAutoConfiguration.class)
public class TraceConfig {

    @Bean
    @ConditionalOnMissingBean(HttpTraceRepository.class)
    public MyHttpTraceRepository traceRepository() {
        return new MyHttpTraceRepository ();
    }
}

这种实现可以记录到请求路径、请求耗时、响应状态、请求Header、响应Header等信息,但没有办法记录请求参数和响应参数。
InMemoryHttpTraceRepository是被HttpTraceFilter拦截器所调用的
所以我们可以写一个自己的HttpTraceFilter拦截器来获取我们需要的请求参数和相应参数

public class HttpTraceFilter extends OncePerRequestFilter implements Ordered {

    // Not LOWEST_PRECEDENCE, but near the end, so it has a good chance of catching all
    // enriched headers, but users can add stuff after this if they want to
    private int order = Ordered.LOWEST_PRECEDENCE - 10;

    private final HttpTraceRepository repository;

    private final HttpExchangeTracer tracer;

    /**
     * Create a new {@link HttpTraceFilter} instance.
     * @param repository the trace repository
     * @param tracer used to trace exchanges
     */
    public HttpTraceFilter(HttpTraceRepository repository, HttpExchangeTracer tracer) {
        this.repository = repository;
        this.tracer = tracer;
    }

    @Override
    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
            HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        if (!isRequestValid(request)) {
            filterChain.doFilter(request, response);
            return;
        }
        TraceableHttpServletRequest traceableRequest = new TraceableHttpServletRequest(
                request);
        HttpTrace trace = this.tracer.receivedRequest(traceableRequest);
        int status = HttpStatus.INTERNAL_SERVER_ERROR.value();
        try {
            filterChain.doFilter(request, response);
            status = response.getStatus();
        }
        finally {
            TraceableHttpServletResponse traceableResponse = new TraceableHttpServletResponse(
                    (status != response.getStatus())
                            ? new CustomStatusResponseWrapper(response, status)
                            : response);
            this.tracer.sendingResponse(trace, traceableResponse,
                    request::getUserPrincipal, () -> getSessionId(request));
            this.repository.add(trace);
        }
    }
    ...
}

自定义HttpTraceFilter

我们自己的过滤器可以仿照HttpTraceFilter继承OncePerRequestFilter,在doFilterInternal这个方法中获取到HttpServletRequest,HttpServletResponse,这样就可以获取到对应的请求参数和返回参数了。
获取get请求参数

String parameterMap = request.getParameterMap()

获取post请求body,body里字符的传输是通过HttpServletRequest中的字节流getInputStream()获得的;而这个字节流在读取了一次之后就不复存在了。这样接口中就没有body中的参数了

String requestBody = IOUtils.toString(request.getInputStream(), Charsets.UTF_8);

可以利用ContentCachingRequestWrapper对HttpServletRequest的请求包一层,该类会将inputstream中的copy一份到自己的字节数组中调用wrappedResponse.copyBodyToResponse();将请求还原。

完整的代码

@Slf4j
public class MyTraceLogFilter extends OncePerRequestFilter implements Ordered {

    private static final String NEED_TRACE_PATH_PREFIX = "/api";
    private static final String IGNORE_CONTENT_TYPE = "multipart/form-data";

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE - 10;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        if (!isRequestValid(request)) {
            filterChain.doFilter(request, response);
            return;
        }
        if (!(request instanceof ContentCachingRequestWrapper)) {
            request = new ContentCachingRequestWrapper(request);
        }
        if (!(response instanceof ContentCachingResponseWrapper)) {
            response = new ContentCachingResponseWrapper(response);
        }
        int status = HttpStatus.INTERNAL_SERVER_ERROR.value();
        long startTime = System.currentTimeMillis();
        try {
            filterChain.doFilter(request, response);
            status = response.getStatus();
        } finally {
            String path = request.getRequestURI();
            if (path.startsWith(NEED_TRACE_PATH_PREFIX) && !Objects.equals(IGNORE_CONTENT_TYPE, request.getContentType())) {

                String requestBody = IOUtils.toString(request.getInputStream(), Charsets.UTF_8);
                log.info(requestBody);
                //1. 记录日志
                HttpTraceLog traceLog = new HttpTraceLog();
                traceLog.setPath(path);
                traceLog.setMethod(request.getMethod());
                long latency = System.currentTimeMillis() - startTime;
                traceLog.setTimeTaken(latency);
                traceLog.setTime(LocalDateTime.now().toString());
                traceLog.setParameterMap(JsonMapper.INSTANCE.toJson(request.getParameterMap()));
                traceLog.setStatus(status);
                traceLog.setRequestBody(getRequestBody(request));
                traceLog.setResponseBody(getResponseBody(response));
                log.info("Http trace log: {}", JsonMapper.INSTANCE.toJson(traceLog));
            }
            updateResponse(response);
        }
    }

    private boolean isRequestValid(HttpServletRequest request) {
        try {
            new URI(request.getRequestURL().toString());
            return true;
        } catch (URISyntaxException ex) {
            return false;
        }
    }

    private String getRequestBody(HttpServletRequest request) {
        String requestBody = "";
        ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
        if (wrapper != null) {
            try {
                requestBody = IOUtils.toString(wrapper.getContentAsByteArray(), wrapper.getCharacterEncoding());
            } catch (IOException e) {
                // NOOP
            }
        }
        return requestBody;
    }

    private String getResponseBody(HttpServletResponse response) {
        String responseBody = "";
        ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
        if (wrapper != null) {
            try {
                responseBody = IOUtils.toString(wrapper.getContentAsByteArray(), wrapper.getCharacterEncoding());
            } catch (IOException e) {
                // NOOP
            }
        }
        return responseBody;
    }

    private void updateResponse(HttpServletResponse response) throws IOException {
        ContentCachingResponseWrapper responseWrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
        Objects.requireNonNull(responseWrapper).copyBodyToResponse();
    }


    @Data
    private static class HttpTraceLog {

        private String path;
        private String parameterMap;
        private String method;
        private Long timeTaken;
        private String time;
        private Integer status;
        private String requestBody;
        private String responseBody;
    }


}

在配置类中注册

​@Configuration
@ConditionalOnWebApplication
public class HttpTraceConfiguration {

    @ConditionalOnWebApplication(type = Type.SERVLET)
    static class ServletTraceFilterConfiguration {

        @Bean
        public MyTraceLogFilter httpTraceLogFilter() {
            return new MyTraceLogFilter ();
        }

    }

}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值