package brave.interop;
import brave.Span;
import brave.Tracing;
import brave.propagation.TraceContextOrSamplingFlags;
import com.github.kristofa.brave.Brave;
import com.github.kristofa.brave.TracerAdapter;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.mockwebserver.Dispatcher;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import zipkin.Endpoint;
import zipkin.storage.InMemoryStorage;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static zipkin.Constants.CLIENT_SEND;
import static zipkin.Constants.SERVER_RECV;
/**
* This is an example of interop between Brave 3 and Brave 4.
*
* <p>This creates a..
* <ol>
* <li>root server span with Brave 3</li>
* <li>one-way child span with Brave 4</li>
* <li>local grandchild span with Brave 3</li>
* </ol>
*
* <p>The key lesson here is that Brave 3 works via thread locals. Via {@link TracerAdapter}, you
* can set or get the current span used in Brave 3.
*/
public class MixedBraveVersionsExample {
@Rule public MockWebServer server = new MockWebServer();
InMemoryStorage storage = new InMemoryStorage();
/** Use different tracers for client and server as usually they are on different hosts. */
Tracing brave4Client = Tracing.newBuilder()
.localEndpoint(Endpoint.builder().serviceName("client").build())
.reporter(s -> storage.spanConsumer().accept(Collections.singletonList(s)))
.build();
Brave brave3Client = TracerAdapter.newBrave(brave4Client.tracer());
Tracing brave4Server = Tracing.newBuilder()
.localEndpoint(Endpoint.builder().serviceName("server").build())
.reporter(s -> storage.spanConsumer().accept(Collections.singletonList(s)))
.build();
Brave brave3Server = TracerAdapter.newBrave(brave4Server.tracer());
CountDownLatch flushedIncomingRequest = new CountDownLatch(1);
@Before public void setup() {
server.setDispatcher(new Dispatcher() {
@Override public MockResponse dispatch(RecordedRequest recordedRequest) {
Span finishedOneWaySpan = joinOneWaySpan(recordedRequest);
attachParentToCurrentThread(finishedOneWaySpan);
// Create an example span using brave 3. The parent should be the one-way span
brave3Server.localTracer().startNewSpan("message-processor", "process");
brave3Server.localTracer().finishSpan();
flushedIncomingRequest.countDown();
// eventhough the client doesn't read the response, we return one
return new MockResponse();
}
});
}
@Test
public void createTraceWithBrave3AndBrave4() throws Exception {
brave3Client.serverTracer().setStateUnknown("get");
brave3Client.serverTracer().setServerReceived();
Span parent = getServerSpanFromBrave3();
createAndPropagateOneWaySpan(parent);
brave3Client.serverTracer().setServerSend(); // close the parent span
// block on the server handling the request, so we can run assertions
flushedIncomingRequest.await();
// And now we have a..
// * root server span created with Brave 3
// * one-way child span created with Brave 4
// * local grandchild span created with Brave 3
List<zipkin.Span> trace = storage.spanStore().getTrace(parent.context().traceId());
assertThat(trace).hasSize(3);
assertThat(trace.get(0).id).isEqualTo(trace.get(1).parentId);
assertThat(trace.get(1).id).isEqualTo(trace.get(2).parentId);
}
/**
* Let's pretend we had an existing trace created by a brave 3 server tracer. In order to use
* Brave 4 in that existing trace, you need to get a reference to the current span.
*/
Span getServerSpanFromBrave3() {
return TracerAdapter.getServerSpan(brave4Client.tracer(),
brave3Client.serverSpanThreadBinder());
}
/**
* This shows how to create a one-way child span using Brave 4. The notable part here is that it
* annotates "cs" and flushes the span.
*/
void createAndPropagateOneWaySpan(Span parent) {
// start a new span representing a request
Span span = brave4Client.tracer().newChild(parent.context());
// inject the trace context into the request
Request.Builder request = new Request.Builder().url(server.url("/"));
brave4Client.propagation().injector(Request.Builder::addHeader).inject(span.context(), request);
// fire off the request asynchronously, totally dropping any response
new OkHttpClient().newCall(request.build()).enqueue(mock(Callback.class));
span.annotate(CLIENT_SEND).flush(); // record the timestamp of the client send and flush
}
/**
* This shows how to join a one-way span using Brave 4. The notable part here is that it annotates
* "sr" and flushes the span. Also notice we return the completed span (to create children).
*/
Span joinOneWaySpan(RecordedRequest recordedRequest) {
TraceContextOrSamplingFlags result =
brave4Server.propagation().extractor(RecordedRequest::getHeader).extract(recordedRequest);
// in real life, we'd guard result.context was set and start a new trace if not
Span serverSpan = brave4Server.tracer().joinSpan(result.context())
.name(recordedRequest.getMethod())
.annotate(SERVER_RECV);
serverSpan.flush(); // record the timestamp of the server receive and flush
return serverSpan;
}
/**
* In order to join traces with Brave 3 tracers, you need to attach a parent span to the the
* current thread. This shows how to attach a Brave 4 span as a parent.
*/
void attachParentToCurrentThread(Span parent) {
TracerAdapter.setServerSpan(parent.context(), brave3Server.serverSpanThreadBinder());
}
}