/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.zipkin;
import java.io.Closeable;
import java.util.EventObject;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.github.kristofa.brave.Brave;
import com.github.kristofa.brave.ClientSpanThreadBinder;
import com.github.kristofa.brave.Sampler;
import com.github.kristofa.brave.ServerSpan;
import com.github.kristofa.brave.ServerSpanThreadBinder;
import com.github.kristofa.brave.SpanCollector;
import com.github.kristofa.brave.scribe.ScribeSpanCollector;
import com.twitter.zipkin.gen.Span;
import org.apache.camel.CamelContext;
import org.apache.camel.CamelContextAware;
import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
import org.apache.camel.Route;
import org.apache.camel.StaticService;
import org.apache.camel.api.management.ManagedAttribute;
import org.apache.camel.api.management.ManagedResource;
import org.apache.camel.component.properties.ServiceHostPropertiesFunction;
import org.apache.camel.component.properties.ServicePortPropertiesFunction;
import org.apache.camel.management.event.ExchangeCompletedEvent;
import org.apache.camel.management.event.ExchangeCreatedEvent;
import org.apache.camel.management.event.ExchangeFailedEvent;
import org.apache.camel.management.event.ExchangeSendingEvent;
import org.apache.camel.management.event.ExchangeSentEvent;
import org.apache.camel.model.RouteDefinition;
import org.apache.camel.spi.RoutePolicy;
import org.apache.camel.spi.RoutePolicyFactory;
import org.apache.camel.support.EventNotifierSupport;
import org.apache.camel.support.RoutePolicySupport;
import org.apache.camel.support.ServiceSupport;
import org.apache.camel.support.SynchronizationAdapter;
import org.apache.camel.util.EndpointHelper;
import org.apache.camel.util.IOHelper;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.ServiceHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.camel.builder.ExpressionBuilder.routeIdExpression;
/**
* To use Zipkin with Camel then setup this {@link ZipkinTracer} in your Camel application.
* <p/>
* Events (span) are captured for incoming and outgoing messages being sent to/from Camel.
* This means you need to configure which which Camel endpoints that maps to zipkin service names.
* The mapping can be configured using
* <ul>
* <li>route id - A Camel route id</li>
* <li>endpoint url - A Camel endpoint url</li>
* </ul>
* For both kinds you can use wildcards and regular expressions to match, which is using the rules from
* {@link EndpointHelper#matchPattern(String, String)} and {@link EndpointHelper#matchEndpoint(CamelContext, String, String)}
* <p/>
* To match all Camel messages you can use <tt>*</tt> in the pattern and configure that to the same service name.
* <br/>
* If no mapping has been configured then Camel will fallback and use endpoint uri's as service names.
* However its recommended to configure service mappings so you can use human logic names instead of Camel
* endpoint uris in the names.
* <p/>
* Camel will auto-configure a {@link ScribeSpanCollector} if no SpanCollector explicit has been configured, and
* if the hostname and port to the span collector has been configured as environment variables
* <ul>
* <li>ZIPKIN_COLLECTOR_THRIFT_SERVICE_HOST - The hostname</li>
* <li>ZIPKIN_COLLECTOR_THRIFT_SERVICE_PORT - The port number</li>
* </ul>
* <p/>
* This class is implemented as both an {@link org.apache.camel.spi.EventNotifier} and {@link RoutePolicy} that allows
* to trap when Camel starts/ends an {@link Exchange} being routed using the {@link RoutePolicy} and during the routing
* if the {@link Exchange} sends messages, then we track them using the {@link org.apache.camel.spi.EventNotifier}.
*/
@ManagedResource(description = "ZipkinTracer")
public class ZipkinTracer extends ServiceSupport implements RoutePolicyFactory, StaticService, CamelContextAware {
private static final Logger LOG = LoggerFactory.getLogger(ZipkinTracer.class);
private static final String ZIPKIN_COLLECTOR_THRIFT_SERVICE = "zipkin-collector-thrift";
private final ZipkinEventNotifier eventNotifier = new ZipkinEventNotifier();
private final Map<String, Brave> braves = new HashMap<>();
private transient boolean useFallbackServiceNames;
private CamelContext camelContext;
private String hostName;
private int port;
private float rate = 1.0f;
private SpanCollector spanCollector;
private Map<String, String> clientServiceMappings = new HashMap<>();
private Map<String, String> serverServiceMappings = new HashMap<>();
private Set<String> excludePatterns = new HashSet<>();
private boolean includeMessageBody;
private boolean includeMessageBodyStreams;
public ZipkinTracer() {
}
@Override
public RoutePolicy createRoutePolicy(CamelContext camelContext, String routeId, RouteDefinition route) {
// ensure this zipkin tracer gets initialized when Camel starts
init(camelContext);
return new ZipkinRoutePolicy(routeId);
}
/**
* Registers this {@link ZipkinTracer} on the {@link CamelContext} if not already registered.
*/
public void init(CamelContext camelContext) {
if (!camelContext.hasService(this)) {
try {
// start this service eager so we init before Camel is starting up
camelContext.addService(this, true, true);
} catch (Exception e) {
throw ObjectHelper.wrapRuntimeCamelException(e);
}
}
}
public CamelContext getCamelContext() {
return camelContext;
}
public void setCamelContext(CamelContext camelContext) {
this.camelContext = camelContext;
}
@ManagedAttribute(description = "The hostname for the remote zipkin server to use.")
public String getHostName() {
return hostName;
}
/**
* Sets a hostname for the remote zipkin server to use.
*/
public void setHostName(String hostName) {
this.hostName = hostName;
}
@ManagedAttribute(description = "The port number for the remote zipkin server to use.")
public int getPort() {
return port;
}
/**
* Sets the port number for the remote zipkin server to use.
*/
public void setPort(int port) {
this.port = port;
}
@ManagedAttribute(description = "Rates how many events should be traced by zipkin. The rate is expressed as a percentage (1.0f = 100%, 0.5f is 50%, 0.1f is 10%).")
public float getRate() {
return rate;
}
/**
* Configures a rate that decides how many events should be traced by zipkin.
* The rate is expressed as a percentage (1.0f = 100%, 0.5f is 50%, 0.1f is 10%).
*
* @param rate minimum sample rate is 0.0001, or 0.01% of traces
*/
public void setRate(float rate) {
this.rate = rate;
}
public SpanCollector getSpanCollector() {
return spanCollector;
}
/**
* The collector to use for sending zipkin span events to the zipkin server.
*/
public void setSpanCollector(SpanCollector spanCollector) {
this.spanCollector = spanCollector;
}
public String getServiceName() {
return clientServiceMappings.get("*");
}
/**
* To use a global service name that matches all Camel events
*/
public void setServiceName(String serviceName) {
clientServiceMappings.put("*", serviceName);
serverServiceMappings.put("*", serviceName);
}
public Map<String, String> getClientServiceMappings() {
return clientServiceMappings;
}
public void setClientServiceMappings(Map<String, String> clientServiceMappings) {
this.clientServiceMappings = clientServiceMappings;
}
/**
* Adds a client service mapping that matches Camel events to the given zipkin service name.
* See more details at the class javadoc.
*
* @param pattern the pattern such as route id, endpoint url
* @param serviceName the zipkin service name
*/
public void addClientServiceMapping(String pattern, String serviceName) {
clientServiceMappings.put(pattern, serviceName);
}
public Map<String, String> getServerServiceMappings() {
return serverServiceMappings;
}
public void setServerServiceMappings(Map<String, String> serverServiceMappings) {
this.serverServiceMappings = serverServiceMappings;
}
/**
* Adds a server service mapping that matches Camel events to the given zipkin service name.
* See more details at the class javadoc.
*
* @param pattern the pattern such as route id, endpoint url
* @param serviceName the zipkin service name
*/
public void addServerServiceMapping(String pattern, String serviceName) {
serverServiceMappings.put(pattern, serviceName);
}
public Set<String> getExcludePatterns() {
return excludePatterns;
}
public void setExcludePatterns(Set<String> excludePatterns) {
this.excludePatterns = excludePatterns;
}
/**
* Adds an exclude pattern that will disable tracing with zipkin for Camel messages that matches the pattern.
*
* @param pattern the pattern such as route id, endpoint url
*/
public void addExcludePattern(String pattern) {
excludePatterns.add(pattern);
}
@ManagedAttribute(description = "Whether to include the Camel message body in the zipkin traces")
public boolean isIncludeMessageBody() {
return includeMessageBody;
}
/**
* Whether to include the Camel message body in the zipkin traces.
* <p/>
* This is not recommended for production usage, or when having big payloads. You can limit the size by
* configuring the <a href="http://camel.apache.org/how-do-i-set-the-max-chars-when-debug-logging-messages-in-camel.html">max debug log size</a>.
* <p/>
* By default message bodies that are stream based are <b>not</b> included. You can use the option {@link #setIncludeMessageBodyStreams(boolean)} to
* turn that on.
*/
@ManagedAttribute(description = "Whether to include the Camel message body in the zipkin traces")
public void setIncludeMessageBody(boolean includeMessageBody) {
this.includeMessageBody = includeMessageBody;
}
@ManagedAttribute(description = "Whether to include stream based Camel message bodies in the zipkin traces")
public boolean isIncludeMessageBodyStreams() {
return includeMessageBodyStreams;
}
/**
* Whether to include message bodies that are stream based in the zipkin traces.
* <p/>
* This requires enabling <a href="http://camel.apache.org/stream-caching.html">stream caching</a> on the routes or globally on the CamelContext.
* <p/>
* This is not recommended for production usage, or when having big payloads. You can limit the size by
* configuring the <a href="http://camel.apache.org/how-do-i-set-the-max-chars-when-debug-logging-messages-in-camel.html">max debug log size</a>.
*/
@ManagedAttribute(description = "Whether to include stream based Camel message bodies in the zipkin traces")
public void setIncludeMessageBodyStreams(boolean includeMessageBodyStreams) {
this.includeMessageBodyStreams = includeMessageBodyStreams;
}
@Override
protected void doStart() throws Exception {
ObjectHelper.notNull(camelContext, "CamelContext", this);
camelContext.getManagementStrategy().addEventNotifier(eventNotifier);
if (!camelContext.getRoutePolicyFactories().contains(this)) {
camelContext.addRoutePolicyFactory(this);
}
if (spanCollector == null) {
if (hostName != null && port > 0) {
LOG.info("Configuring Zipkin ScribeSpanCollector using host: {} and port: {}", hostName, port);
spanCollector = new ScribeSpanCollector(hostName, port);
} else {
// is there a zipkin service setup as ENV variable to auto register a scribe span collector
String host = new ServiceHostPropertiesFunction().apply(ZIPKIN_COLLECTOR_THRIFT_SERVICE);
String port = new ServicePortPropertiesFunction().apply(ZIPKIN_COLLECTOR_THRIFT_SERVICE);
if (ObjectHelper.isNotEmpty(host) && ObjectHelper.isNotEmpty(port)) {
LOG.info("Auto-configuring Zipkin ScribeSpanCollector using host: {} and port: {}", host, port);
int num = camelContext.getTypeConverter().mandatoryConvertTo(Integer.class, port);
spanCollector = new ScribeSpanCollector(host, num);
}
}
}
if (spanCollector == null) {
// Try to lookup the span collector from the registry if only one instance is present
Set<SpanCollector> collectors = camelContext.getRegistry().findByType(SpanCollector.class);
if (collectors.size() == 1) {
spanCollector = collectors.iterator().next();
}
}
ObjectHelper.notNull(spanCollector, "SpanCollector", this);
if (clientServiceMappings.isEmpty() && serverServiceMappings.isEmpty()) {
LOG.warn("No service name(s) has been mapped in clientServiceMappings or serverServiceMappings. Camel will fallback and use endpoint uris as service names.");
useFallbackServiceNames = true;
}
// create braves mapped per service name
for (Map.Entry<String, String> entry : clientServiceMappings.entrySet()) {
String pattern = entry.getKey();
String serviceName = entry.getValue();
createBraveForService(pattern, serviceName);
}
for (Map.Entry<String, String> entry : serverServiceMappings.entrySet()) {
String pattern = entry.getKey();
String serviceName = entry.getValue();
createBraveForService(pattern, serviceName);
}
ServiceHelper.startServices(spanCollector, eventNotifier);
}
@Override
protected void doStop() throws Exception {
// stop event notifier
camelContext.getManagementStrategy().removeEventNotifier(eventNotifier);
ServiceHelper.stopService(eventNotifier);
// stop and close collector
ServiceHelper.stopAndShutdownService(spanCollector);
if (spanCollector instanceof Closeable) {
IOHelper.close((Closeable) spanCollector);
}
// clear braves
braves.clear();
// remove route policy
camelContext.getRoutePolicyFactories().remove(this);
}
private String getServiceName(Exchange exchange, Endpoint endpoint, boolean server, boolean client) {
if (client) {
return getServiceName(exchange, endpoint, clientServiceMappings);
} else if (server) {
return getServiceName(exchange, endpoint, serverServiceMappings);
} else {
return null;
}
}
private String getServiceName(Exchange exchange, Endpoint endpoint, Map<String, String> serviceMappings) {
String answer = null;
// endpoint takes precedence over route
if (endpoint != null) {
String url = endpoint.getEndpointUri();
if (url != null) {
// exclude patterns take precedence
for (String pattern : excludePatterns) {
if (EndpointHelper.matchEndpoint(exchange.getContext(), url, pattern)) {
return null;
}
}
for (Map.Entry<String, String> entry : serviceMappings.entrySet()) {
String pattern = entry.getKey();
if (EndpointHelper.matchEndpoint(exchange.getContext(), url, pattern)) {
answer = entry.getValue();
break;
}
}
}
}
// route
if (answer == null) {
String id = routeIdExpression().evaluate(exchange, String.class);
if (id != null) {
// exclude patterns take precedence
for (String pattern : excludePatterns) {
if (EndpointHelper.matchPattern(id, pattern)) {
return null;
}
}
for (Map.Entry<String, String> entry : serviceMappings.entrySet()) {
String pattern = entry.getKey();
if (EndpointHelper.matchPattern(id, pattern)) {
answer = entry.getValue();
break;
}
}
}
}
if (answer == null) {
String id = exchange.getFromRouteId();
if (id != null) {
// exclude patterns take precedence
for (String pattern : excludePatterns) {
if (EndpointHelper.matchPattern(id, pattern)) {
return null;
}
}
for (Map.Entry<String, String> entry : serviceMappings.entrySet()) {
String pattern = entry.getKey();
if (EndpointHelper.matchPattern(id, pattern)) {
answer = entry.getValue();
break;
}
}
}
}
if (answer == null && useFallbackServiceNames) {
String key = null;
if (endpoint != null) {
key = endpoint.getEndpointKey();
} else if (exchange.getFromEndpoint() != null) {
key = exchange.getFromEndpoint().getEndpointKey();
}
// exclude patterns take precedence
for (String pattern : excludePatterns) {
if (EndpointHelper.matchPattern(key, pattern)) {
return null;
}
}
if (LOG.isTraceEnabled() && key != null) {
LOG.trace("Using serviceName: {} as fallback", key);
}
return key;
} else {
if (LOG.isTraceEnabled() && answer != null) {
LOG.trace("Using serviceName: {}", answer);
}
return answer;
}
}
private void createBraveForService(String pattern, String serviceName) {
Brave brave = braves.get(pattern);
if (brave == null && !braves.containsKey(serviceName)) {
Brave.Builder builder = new Brave.Builder(serviceName);
builder = builder.traceSampler(Sampler.create(rate));
if (spanCollector != null) {
builder = builder.spanCollector(spanCollector);
}
brave = builder.build();
braves.put(serviceName, brave);
}
}
private Brave getBrave(String serviceName) {
Brave brave = null;
if (serviceName != null) {
brave = braves.get(serviceName);
if (brave == null && useFallbackServiceNames) {
LOG.debug("Creating Brave assigned to serviceName: {}", serviceName + " as fallback");
Brave.Builder builder = new Brave.Builder(serviceName);
builder = builder.traceSampler(Sampler.create(rate));
if (spanCollector != null) {
builder = builder.spanCollector(spanCollector);
}
brave = builder.build();
braves.put(serviceName, brave);
}
}
return brave;
}
private void clientRequest(Brave brave, String serviceName, ExchangeSendingEvent event) {
ClientSpanThreadBinder clientBinder = brave.clientSpanThreadBinder();
ServerSpanThreadBinder serverBinder = brave.serverSpanThreadBinder();
// reuse existing span if we do multiple requests from the same
ZipkinState state = event.getExchange().getProperty(ZipkinState.KEY, ZipkinState.class);
if (state == null) {
state = new ZipkinState();
event.getExchange().setProperty(ZipkinState.KEY, state);
}
// if we started from a server span then lets reuse that when we call a downstream service
ServerSpan last = state.peekServerSpan();
if (last != null) {
serverBinder.setCurrentSpan(last);
}
brave.clientRequestInterceptor().handle(new ZipkinClientRequestAdapter(this, serviceName, event.getExchange(), event.getEndpoint()));
// store span after request
Span span = clientBinder.getCurrentClientSpan();
state.pushClientSpan(span);
// and reset binder
clientBinder.setCurrentSpan(null);
serverBinder.setCurrentSpan(null);
if (span != null && LOG.isDebugEnabled()) {
String traceId = "" + span.getTrace_id();
String spanId = "" + span.getId();
String parentId = span.getParent_id() != null ? "" + span.getParent_id() : null;
if (LOG.isDebugEnabled()) {
if (parentId != null) {
LOG.debug(String.format("clientRequest [service=%s, traceId=%20s, spanId=%20s, parentId=%20s]", serviceName, traceId, spanId, parentId));
} else {
LOG.debug(String.format("clientRequest [service=%s, traceId=%20s, spanId=%20s]", serviceName, traceId, spanId));
}
}
}
}
private void clientResponse(Brave brave, String serviceName, ExchangeSentEvent event) {
Span span = null;
ZipkinState state = event.getExchange().getProperty(ZipkinState.KEY, ZipkinState.class);
if (state != null) {
// only process if it was a zipkin client event
span = state.popClientSpan();
}
if (span != null) {
ClientSpanThreadBinder clientBinder = brave.clientSpanThreadBinder();
clientBinder.setCurrentSpan(span);
brave.clientResponseInterceptor().handle(new ZipkinClientResponseAdaptor(this, event.getExchange(), event.getEndpoint()));
// and reset binder
clientBinder.setCurrentSpan(null);
if (LOG.isDebugEnabled()) {
String traceId = "" + span.getTrace_id();
String spanId = "" + span.getId();
String parentId = span.getParent_id() != null ? "" + span.getParent_id() : null;
if (LOG.isDebugEnabled()) {
if (parentId != null) {
LOG.debug(String.format("clientResponse[service=%s, traceId=%20s, spanId=%20s, parentId=%20s]", serviceName, traceId, spanId, parentId));
} else {
LOG.debug(String.format("clientResponse[service=%s, traceId=%20s, spanId=%20s]", serviceName, traceId, spanId));
}
}
}
}
}
private ServerSpan serverRequest(Brave brave, String serviceName, Exchange exchange) {
ServerSpanThreadBinder serverBinder = brave.serverSpanThreadBinder();
// reuse existing span if we do multiple requests from the same
ZipkinState state = exchange.getProperty(ZipkinState.KEY, ZipkinState.class);
if (state == null) {
state = new ZipkinState();
exchange.setProperty(ZipkinState.KEY, state);
}
// if we started from a another server span then lets reuse that
ServerSpan last = state.peekServerSpan();
if (last != null) {
serverBinder.setCurrentSpan(last);
}
brave.serverRequestInterceptor().handle(new ZipkinServerRequestAdapter(this, exchange));
// store span after request
ServerSpan span = serverBinder.getCurrentServerSpan();
state.pushServerSpan(span);
// and reset binder
serverBinder.setCurrentSpan(null);
if (span != null && span.getSpan() != null && LOG.isDebugEnabled()) {
String traceId = "" + span.getSpan().getTrace_id();
String spanId = "" + span.getSpan().getId();
String parentId = span.getSpan().getParent_id() != null ? "" + span.getSpan().getParent_id() : null;
if (LOG.isDebugEnabled()) {
if (parentId != null) {
LOG.debug(String.format("serverRequest [service=%s, traceId=%20s, spanId=%20s, parentId=%20s]", serviceName, traceId, spanId, parentId));
} else {
LOG.debug(String.format("serverRequest [service=%s, traceId=%20s, spanId=%20s]", serviceName, traceId, spanId));
}
}
}
return span;
}
private void serverResponse(Brave brave, String serviceName, Exchange exchange) {
ServerSpan span = null;
ZipkinState state = exchange.getProperty(ZipkinState.KEY, ZipkinState.class);
if (state != null) {
// only process if it was a zipkin server event
span = state.popServerSpan();
}
if (span != null) {
ServerSpanThreadBinder serverBinder = brave.serverSpanThreadBinder();
serverBinder.setCurrentSpan(span);
brave.serverResponseInterceptor().handle(new ZipkinServerResponseAdapter(this, exchange));
// and reset binder
serverBinder.setCurrentSpan(null);
if (span.getSpan() != null && LOG.isDebugEnabled()) {
String traceId = "" + span.getSpan().getTrace_id();
String spanId = "" + span.getSpan().getId();
String parentId = span.getSpan().getParent_id() != null ? "" + span.getSpan().getParent_id() : null;
if (LOG.isDebugEnabled()) {
if (parentId != null) {
LOG.debug(String.format("serverResponse[service=%s, traceId=%20s, spanId=%20s, parentId=%20s]", serviceName, traceId, spanId, parentId));
} else {
LOG.debug(String.format("serverResponse[service=%s, traceId=%20s, spanId=%20s]", serviceName, traceId, spanId));
}
}
}
}
}
private boolean hasZipkinTraceId(Exchange exchange) {
// must have zipkin headers to start a server event
return exchange.getIn().getHeader(ZipkinConstants.TRACE_ID) != null;
}
private final class ZipkinEventNotifier extends EventNotifierSupport {
@Override
public void notify(EventObject event) throws Exception {
// use event notifier to track events when Camel messages to endpoints
// these events corresponds to Zipkin client events
// client events
if (event instanceof ExchangeSendingEvent) {
ExchangeSendingEvent ese = (ExchangeSendingEvent) event;
String serviceName = getServiceName(ese.getExchange(), ese.getEndpoint(), false, true);
Brave brave = getBrave(serviceName);
if (brave != null) {
clientRequest(brave, serviceName, ese);
}
} else if (event instanceof ExchangeSentEvent) {
ExchangeSentEvent ese = (ExchangeSentEvent) event;
String serviceName = getServiceName(ese.getExchange(), ese.getEndpoint(), false, true);
Brave brave = getBrave(serviceName);
if (brave != null) {
clientResponse(brave, serviceName, ese);
}
}
}
@Override
public boolean isEnabled(EventObject event) {
return event instanceof ExchangeSendingEvent
|| event instanceof ExchangeSentEvent
|| event instanceof ExchangeCreatedEvent
|| event instanceof ExchangeCompletedEvent
|| event instanceof ExchangeFailedEvent;
}
@Override
public String toString() {
return "ZipkinEventNotifier";
}
}
private final class ZipkinRoutePolicy extends RoutePolicySupport {
private final String routeId;
ZipkinRoutePolicy(String routeId) {
this.routeId = routeId;
}
@Override
public void onExchangeBegin(Route route, Exchange exchange) {
// use route policy to track events when Camel a Camel route begins/end the lifecycle of an Exchange
// these events corresponds to Zipkin server events
if (hasZipkinTraceId(exchange)) {
String serviceName = getServiceName(exchange, route.getEndpoint(), true, false);
Brave brave = getBrave(serviceName);
if (brave != null) {
serverRequest(brave, serviceName, exchange);
}
}
// add on completion after the route is done, but before the consumer writes the response
// this allows us to track the zipkin event before returning the response which is the right time
exchange.addOnCompletion(new SynchronizationAdapter() {
@Override
public void onAfterRoute(Route route, Exchange exchange) {
String serviceName = getServiceName(exchange, route.getEndpoint(), true, false);
Brave brave = getBrave(serviceName);
if (brave != null) {
serverResponse(brave, serviceName, exchange);
}
}
@Override
public String toString() {
return "ZipkinTracerOnCompletion[" + routeId + "]";
}
});
}
}
}