序
本文主要研究下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 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 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 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 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, Supplier 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 includes;
public HttpTraceWebFilter(HttpTraceRepository repository, HttpExchangeTracer tracer,
Set 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 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 asType(Object object, Class type) {
if (type.isInstance(object)) {
return type.cast(object);
}
return null;
}
private Mono 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 traces;
private HttpTraceDescriptor(List traces) {
this.traces = traces;
}
public List 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
- Spring Boot Reference Guide