package com.github.kristofa.brave.mysql; import com.github.kristofa.brave.ClientTracer; import com.mysql.jdbc.Connection; import com.mysql.jdbc.PreparedStatement; import com.mysql.jdbc.ResultSetInternalMethods; import com.mysql.jdbc.Statement; import com.mysql.jdbc.StatementInterceptorV2; import com.twitter.zipkin.gen.Endpoint; import zipkin.Constants; import zipkin.TraceKeys; import java.net.Inet4Address; import java.net.InetAddress; import java.net.URI; import java.nio.ByteBuffer; import java.sql.SQLException; import java.util.Properties; /** * <p> * A MySQL statement interceptor that will report to Zipkin how long each statement takes. * </p> * <p> * To use it, append <code>?statementInterceptors=com.github.kristofa.brave.mysql.MySQLStatementInterceptor</code> to the end of the connection url. * </p> * <p> * Note that this class must be injected with the {@linkplain ClientTracer} to use to communicate with Zipkin via the {@linkplain #setClientTracer} method; * this is normally done by the {@linkplain MySQLStatementInterceptorManagementBean}. * </p> * * @deprecated Replaced by {@code TracingStatementInterceptor} from brave-instrumentation-mysql */ @Deprecated public class MySQLStatementInterceptor implements StatementInterceptorV2 { private final static String SERVICE_NAME_KEY = "zipkinServiceName"; // TODO: is static scope best? ex. preferred to thread local etc? static volatile ClientTracer clientTracer; public static void setClientTracer(final ClientTracer tracer) { clientTracer = tracer; } @Override public ResultSetInternalMethods preProcess(final String sql, final Statement interceptedStatement, final Connection connection) throws SQLException { ClientTracer clientTracer = MySQLStatementInterceptor.clientTracer; if (clientTracer != null) { final String sqlToLog; // When running a prepared statement, sql will be null and we must fetch the sql from the statement itself if (interceptedStatement instanceof PreparedStatement) { sqlToLog = ((PreparedStatement) interceptedStatement).getPreparedSql(); } else { sqlToLog = sql; } beginTrace(clientTracer, sqlToLog, connection); } return null; } @Override public ResultSetInternalMethods postProcess(final String sql, final Statement interceptedStatement, final ResultSetInternalMethods originalResultSet, final Connection connection, final int warningCount, final boolean noIndexUsed, final boolean noGoodIndexUsed, final SQLException statementException) throws SQLException { ClientTracer clientTracer = MySQLStatementInterceptor.clientTracer; if (clientTracer != null) { endTrace(clientTracer, warningCount, statementException); } return null; } private void beginTrace(final ClientTracer tracer, final String sql, final Connection connection) throws SQLException { tracer.startNewSpan("query"); tracer.submitBinaryAnnotation(TraceKeys.SQL_QUERY, sql); try { setClientSent(tracer, connection); } catch (Exception e) { // logging the server address is optional tracer.setClientSent(); } } /** * MySQL exposes the host connecting to, but not the port. This attempts to get the port from the * JDBC URL. Ex. 5555 from {@code jdbc:mysql://localhost:5555/isSampled}, or 3306 if absent. */ private void setClientSent(ClientTracer tracer, Connection connection) throws Exception { InetAddress address = Inet4Address.getByName(connection.getHost()); int ipv4 = ByteBuffer.wrap(address.getAddress()).getInt(); URI url = URI.create(connection.getMetaData().getURL().substring(5)); // strip "jdbc:" int port = url.getPort() == -1 ? 3306 : url.getPort(); Properties props = connection.getProperties(); String serviceName = props.getProperty(SERVICE_NAME_KEY); if (serviceName == null || "".equals(serviceName)) { serviceName = "mysql"; String databaseName = connection.getCatalog(); if (databaseName != null && !"".equals(databaseName)) { serviceName += "-" + databaseName; } } tracer.setClientSent(Endpoint.builder() .ipv4(ipv4).port(port).serviceName(serviceName).build()); } private void endTrace(final ClientTracer tracer, final int warningCount, final SQLException statementException) { try { if (warningCount > 0) { tracer.submitBinaryAnnotation("warning.count", Integer.toString(warningCount)); } if (statementException != null) { tracer.submitBinaryAnnotation(Constants.ERROR, Integer.toString(statementException.getErrorCode())); } } finally { tracer.setClientReceived(); } } @Override public boolean executeTopLevelOnly() { // True means that we don't get notified about queries that other interceptors issue return true; } @Override public void init(final Connection connection, final Properties properties) throws SQLException { // Don't care } @Override public void destroy() { // Don't care } }