聊聊springboot2的httptrace

codecraft

本文主要研究下springboot2的httptrace

HttpTraceAutoConfiguration

spring-boot-actuator-autoconfigure-2.0.1.RELEASE-sources.jar!/org/springframework/boot/actuate/autoconfigure/trace/http/HttpTraceAutoConfiguration.java

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

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

    @Bean
    @ConditionalOnMissingBean
    public HttpExchangeTracer httpExchangeTracer(HttpTraceProperties traceProperties) {
        return new HttpExchangeTracer(traceProperties.getInclude());
    }

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

        @Bean
        @ConditionalOnMissingBean
        public HttpTraceFilter httpTraceFilter(HttpTraceRepository repository,
                HttpExchangeTracer tracer) {
            return new HttpTraceFilter(repository, tracer);
        }

    }

    @ConditionalOnWebApplication(type = Type.REACTIVE)
    static class ReactiveTraceFilterConfiguration {

        @Bean
        @ConditionalOnMissingBean
        public HttpTraceWebFilter httpTraceWebFilter(HttpTraceRepository repository,
                HttpExchangeTracer tracer, HttpTraceProperties traceProperties) {
            return new HttpTraceWebFilter(repository, tracer,
                    traceProperties.getInclude());
        }

    }

}
可以看到这里按servlet及reactive两种方式分别注入不同的filter,servlet的是HttpTraceFilter,reactive的是HttpTraceWebFilter
无论是servlet方式还是reactive方式,都会注入HttpTraceRepository以及HttpExchangeTracer

InMemoryHttpTraceRepository

spring-boot-actuator-2.0.1.RELEASE-sources.jar!/org/springframework/boot/actuate/trace/http/InMemoryHttpTraceRepository.java

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);
            }
        }
    }

}
这里默认设置了只存最近100请求记录,不然这个使用synchronized,貌似不是很高效

HttpExchangeTracer

spring-boot-actuator-2.0.1.RELEASE-sources.jar!/org/springframework/boot/actuate/trace/http/HttpExchangeTracer.java

public class HttpExchangeTracer {

    private final Set<Include> includes;

    /**
     * Creates a new {@code HttpExchangeTracer} that will use the given {@code includes}
     * to determine the contents of its traces.
     * @param includes the includes
     */
    public HttpExchangeTracer(Set<Include> includes) {
        this.includes = includes;
    }

    /**
     * Begins the tracing of the exchange that was initiated by the given {@code request}
     * being received.
     * @param request the received request
     * @return the HTTP trace for the
     */
    public final HttpTrace receivedRequest(TraceableRequest request) {
        return new HttpTrace(new FilteredTraceableRequest(request));
    }

    /**
     * Ends the tracing of the exchange that is being concluded by sending the given
     * {@code response}.
     * @param trace the trace for the exchange
     * @param response the response that concludes the exchange
     * @param principal a supplier for the exchange's principal
     * @param sessionId a supplier for the id of the exchange's session
     */
    public final void sendingResponse(HttpTrace trace, TraceableResponse response,
            Supplier<Principal> principal, Supplier<String> sessionId) {
        setIfIncluded(Include.TIME_TAKEN,
                () -> System.currentTimeMillis() - trace.getTimestamp().toEpochMilli(),
                trace::setTimeTaken);
        setIfIncluded(Include.SESSION_ID, sessionId, trace::setSessionId);
        setIfIncluded(Include.PRINCIPAL, principal, trace::setPrincipal);
        trace.setResponse(
                new HttpTrace.Response(new FilteredTraceableResponse(response)));
    }
    //......
}
HttpExchangeTracer是一个简易的tracer,主要是receivedRequest方法记录请求生成HttpTrace,然后sendingResponse结束本次trace,并将记录添加到HttpTrace

Filter

HttpTraceFilter

spring-boot-actuator-2.0.1.RELEASE-sources.jar!/org/springframework/boot/actuate/web/trace/servlet/HttpTraceFilter.java

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 {
        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() ? response
                            : new CustomStatusResponseWrapper(response, status));
            this.tracer.sendingResponse(trace, traceableResponse,
                    request::getUserPrincipal, () -> getSessionId(request));
            this.repository.add(trace);
        }
    }

    private String getSessionId(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        return session == null ? null : session.getId();
    }

    private static final class CustomStatusResponseWrapper
            extends HttpServletResponseWrapper {

        private final int status;

        private CustomStatusResponseWrapper(HttpServletResponse response, int status) {
            super(response);
            this.status = status;
        }

        @Override
        public int getStatus() {
            return this.status;
        }

    }

}
可以看到继承的是OncePerRequestFilter

HttpTraceWebFilter

spring-boot-actuator-2.0.1.RELEASE-sources.jar!/org/springframework/boot/actuate/web/trace/reactive/HttpTraceWebFilter.java

public class HttpTraceWebFilter implements WebFilter, Ordered {

    private static final Object NONE = new Object();

    // 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;

    private final Set<Include> includes;

    public HttpTraceWebFilter(HttpTraceRepository repository, HttpExchangeTracer tracer,
            Set<Include> includes) {
        this.repository = repository;
        this.tracer = tracer;
        this.includes = includes;
    }

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

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

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        Mono<?> principal = this.includes.contains(Include.PRINCIPAL)
                ? exchange.getPrincipal().cast(Object.class).defaultIfEmpty(NONE)
                : Mono.just(NONE);
        Mono<?> session = this.includes.contains(Include.SESSION_ID)
                ? exchange.getSession() : Mono.just(NONE);
        return Mono.zip(principal, session)
                .flatMap((tuple) -> filter(exchange, chain,
                        asType(tuple.getT1(), Principal.class),
                        asType(tuple.getT2(), WebSession.class)));
    }

    private <T> T asType(Object object, Class<T> type) {
        if (type.isInstance(object)) {
            return type.cast(object);
        }
        return null;
    }

    private Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain,
            Principal principal, WebSession session) {
        ServerWebExchangeTraceableRequest request = new ServerWebExchangeTraceableRequest(
                exchange);
        HttpTrace trace = this.tracer.receivedRequest(request);
        return chain.filter(exchange).doAfterSuccessOrError((aVoid, ex) -> {
            this.tracer.sendingResponse(trace,
                    new TraceableServerHttpResponse(ex == null ? exchange.getResponse()
                            : new CustomStatusResponseDecorator(ex,
                                    exchange.getResponse())),
                    () -> principal, () -> getStartedSessionId(session));
            this.repository.add(trace);
        });
    }

    private String getStartedSessionId(WebSession session) {
        return (session != null && session.isStarted()) ? session.getId() : null;
    }

    private static final class CustomStatusResponseDecorator
            extends ServerHttpResponseDecorator {

        private final HttpStatus status;

        private CustomStatusResponseDecorator(Throwable ex, ServerHttpResponse delegate) {
            super(delegate);
            this.status = ex instanceof ResponseStatusException
                    ? ((ResponseStatusException) ex).getStatus()
                    : HttpStatus.INTERNAL_SERVER_ERROR;
        }

        @Override
        public HttpStatus getStatusCode() {
            return this.status;
        }

    }

}
可以看到继承的是WebFilter

Endpoint

HttpTraceEndpointAutoConfiguration

spring-boot-actuator-autoconfigure-2.0.1.RELEASE-sources.jar!/org/springframework/boot/actuate/autoconfigure/trace/http/HttpTraceEndpointAutoConfiguration.java

@Configuration
@AutoConfigureAfter(HttpTraceAutoConfiguration.class)
public class HttpTraceEndpointAutoConfiguration {

    @Bean
    @ConditionalOnBean(HttpTraceRepository.class)
    @ConditionalOnMissingBean
    @ConditionalOnEnabledEndpoint
    public HttpTraceEndpoint httpTraceEndpoint(HttpTraceRepository traceRepository) {
        return new HttpTraceEndpoint(traceRepository);
    }

}
这里使用traceRepository创建了HttpTraceEndpoint

HttpTraceEndpoint

spring-boot-actuator-2.0.1.RELEASE-sources.jar!/org/springframework/boot/actuate/trace/http/HttpTraceEndpoint.java

/**
 * {@link Endpoint} to expose {@link HttpTrace} information.
 *
 * @author Dave Syer
 * @author Andy Wilkinson
 * @since 2.0.0
 */
@Endpoint(id = "httptrace")
public class HttpTraceEndpoint {

    private final HttpTraceRepository repository;

    /**
     * Create a new {@link HttpTraceEndpoint} instance.
     * @param repository the trace repository
     */
    public HttpTraceEndpoint(HttpTraceRepository repository) {
        Assert.notNull(repository, "Repository must not be null");
        this.repository = repository;
    }

    @ReadOperation
    public HttpTraceDescriptor traces() {
        return new HttpTraceDescriptor(this.repository.findAll());
    }

    /**
     * A description of an application's {@link HttpTrace} entries. Primarily intended for
     * serialization to JSON.
     */
    public static final class HttpTraceDescriptor {

        private final List<HttpTrace> traces;

        private HttpTraceDescriptor(List<HttpTrace> traces) {
            this.traces = traces;
        }

        public List<HttpTrace> getTraces() {
            return this.traces;
        }

    }

}

输出实例

{
  "traces": [
    {
      "timestamp": "2018-04-21T14:14:36.256Z",
      "principal": null,
      "session": null,
      "request": {
        "method": "GET",
        "uri": "http://localhost:8080/actuator",
        "headers": {
          "host": [
            "localhost:8080"
          ],
          "connection": [
            "keep-alive"
          ],
          "upgrade-insecure-requests": [
            "1"
          ],
          "user-agent": [
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36"
          ],
          "accept": [
            "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"
          ],
          "accept-encoding": [
            "gzip, deflate, br"
          ],
          "accept-language": [
            "zh-CN,zh;q=0.9,en;q=0.8"
          ],
          "cookie": [
            "hibext_instdsigdipv2=1; _ga=GA1.1.933052261.1524234775; _gid=GA1.1.1398833521.1524234775"
          ]
        },
        "remoteAddress": null
      },
      "response": {
        "status": 200,
        "headers": {
          "Content-Type": [
            "application/vnd.spring-boot.actuator.v2+json;charset=UTF-8"
          ],
          "Transfer-Encoding": [
            "chunked"
          ],
          "Date": [
            "Sat, 21 Apr 2018 14:14:36 GMT"
          ]
        }
      },
      "timeTaken": 110
    }
  ]
}

小结

httptrace默认是开的,对于servlet及reactive的方式,分别使用了不同的filter,前者是HttpTraceFilter继承了OncePerRequestFilter,后者是HttpTraceWebFilter继承了WebFilter。可以自定义替换掉InMemoryHttpTraceRepository,自己将请求日志异步传递到日志中心。

doc

阅读 4.5k

code-craft
spring boot , docker and so on 欢迎关注微信公众号: geek_luandun

当一个代码的工匠回首往事时,不因虚度年华而悔恨,也不因碌碌无为而羞愧,这样,当他老的时候,可以很...

11.6k 声望
1.9k 粉丝
0 条评论

当一个代码的工匠回首往事时,不因虚度年华而悔恨,也不因碌碌无为而羞愧,这样,当他老的时候,可以很...

11.6k 声望
1.9k 粉丝
文章目录
宣传栏