聊聊springboot2的httptrace

本文主要研究下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

猜你喜欢

转载自my.oschina.net/go4it/blog/1799078