package brave.mysql;
import brave.Tracer.SpanInScope;
import brave.Tracing;
import brave.internal.StrictCurrentTraceContext;
import brave.sampler.Sampler;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.concurrent.ConcurrentLinkedDeque;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import zipkin.Constants;
import zipkin.Span;
import zipkin.TraceKeys;
import zipkin.internal.MergeById;
import zipkin.internal.Util;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
import static org.junit.Assume.assumeTrue;
import static zipkin.internal.Util.envOr;
public class ITTracingStatementInterceptor {
static final String QUERY = "select 'hello world'";
ConcurrentLinkedDeque<Span> spans = new ConcurrentLinkedDeque<>();
Tracing tracing = tracingBuilder(Sampler.ALWAYS_SAMPLE).build();
Connection connection;
@Before public void init() throws SQLException {
StringBuilder url = new StringBuilder("jdbc:mysql://");
url.append(envOr("MYSQL_HOST", "127.0.0.1"));
url.append(":").append(envOr("MYSQL_TCP_PORT", 3306));
String db = envOr("MYSQL_DB", null);
if (db != null) url.append("/").append(db);
url.append("?statementInterceptors=").append(TracingStatementInterceptor.class.getName());
url.append("&zipkinServiceName=").append("myservice");
MysqlDataSource dataSource = new MysqlDataSource();
dataSource.setUrl(url.toString());
dataSource.setUser(System.getenv("MYSQL_USER"));
assumeTrue("Minimally, the environment variable MYSQL_USER must be set",
dataSource.getUser() != null);
dataSource.setPassword(envOr("MYSQL_PASS", ""));
connection = dataSource.getConnection();
spans.clear();
}
@After public void close() throws SQLException {
Tracing.current().close();
if (connection != null) connection.close();
}
@Test
public void makesChildOfCurrentSpan() throws Exception {
brave.Span parent = tracing.tracer().newTrace().name("test").start();
try (SpanInScope ws = tracing.tracer().withSpanInScope(parent)) {
prepareExecuteSelect(QUERY);
} finally {
parent.finish();
}
assertThat(spans)
.hasSize(2);
}
@Test
public void reportsClientAnnotationsToZipkin() throws Exception {
prepareExecuteSelect(QUERY);
assertThat(spans)
.flatExtracting(s -> s.annotations)
.extracting(a -> a.value)
.containsExactly("cs", "cr");
}
@Test
public void defaultSpanNameIsOperationName() throws Exception {
prepareExecuteSelect(QUERY);
assertThat(spans)
.extracting(s -> s.name)
.containsExactly("select");
}
@Test
public void addsQueryTag() throws Exception {
prepareExecuteSelect(QUERY);
assertThat(spans)
.flatExtracting(s -> s.binaryAnnotations)
.filteredOn(a -> a.key.equals(TraceKeys.SQL_QUERY))
.extracting(a -> new String(a.value, Util.UTF_8))
.containsExactly(QUERY);
}
@Test
public void reportsServerAddress() throws Exception {
prepareExecuteSelect(QUERY);
assertThat(spans)
.flatExtracting(s -> s.binaryAnnotations)
.extracting(b -> b.key, b -> b.endpoint.serviceName)
.contains(tuple(Constants.SERVER_ADDR, "myservice"));
}
void prepareExecuteSelect(String query) throws SQLException {
try (PreparedStatement ps = connection.prepareStatement(query)) {
try (ResultSet resultSet = ps.executeQuery()) {
while (resultSet.next()) {
resultSet.getString(1);
}
}
}
}
Tracing.Builder tracingBuilder(Sampler sampler) {
return Tracing.newBuilder()
.reporter(spans::add)
.currentTraceContext(new StrictCurrentTraceContext())
.sampler(sampler);
}
}