本文主要研究下springboot2的httptracehtml
spring-boot-actuator-autoconfigure-2.0.1.RELEASE-sources.jar!/org/springframework/boot/actuate/autoconfigure/trace/http/HttpTraceAutoConfiguration.javajava
@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
spring-boot-actuator-2.0.1.RELEASE-sources.jar!/org/springframework/boot/actuate/trace/http/InMemoryHttpTraceRepository.javareact
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,貌似不是很高效
spring-boot-actuator-2.0.1.RELEASE-sources.jar!/org/springframework/boot/actuate/trace/http/HttpExchangeTracer.javaweb
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
spring-boot-actuator-2.0.1.RELEASE-sources.jar!/org/springframework/boot/actuate/web/trace/servlet/HttpTraceFilter.javaspring
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
spring-boot-actuator-2.0.1.RELEASE-sources.jar!/org/springframework/boot/actuate/web/trace/reactive/HttpTraceWebFilter.javajson
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
spring-boot-actuator-autoconfigure-2.0.1.RELEASE-sources.jar!/org/springframework/boot/actuate/autoconfigure/trace/http/HttpTraceEndpointAutoConfiguration.javaspringboot
@Configuration @AutoConfigureAfter(HttpTraceAutoConfiguration.class) public class HttpTraceEndpointAutoConfiguration { @Bean @ConditionalOnBean(HttpTraceRepository.class) @ConditionalOnMissingBean @ConditionalOnEnabledEndpoint public HttpTraceEndpoint httpTraceEndpoint(HttpTraceRepository traceRepository) { return new HttpTraceEndpoint(traceRepository); } }
這裏使用traceRepository建立了HttpTraceEndpoint
spring-boot-actuator-2.0.1.RELEASE-sources.jar!/org/springframework/boot/actuate/trace/http/HttpTraceEndpoint.javacookie
/** * {@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; } } }
輸出實例session
{ "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,本身將請求日誌異步傳遞到日誌中心。app