package org.stagemonitor.jdbc;
import com.p6spy.engine.common.ConnectionInformation;
import com.p6spy.engine.common.StatementInformation;
import com.p6spy.engine.event.SimpleJdbcEventListener;
import com.uber.jaeger.context.TracingUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.stagemonitor.configuration.ConfigurationRegistry;
import org.stagemonitor.core.CorePlugin;
import org.stagemonitor.core.Stagemonitor;
import org.stagemonitor.core.metrics.metrics2.Metric2Registry;
import org.stagemonitor.core.metrics.metrics2.MetricName;
import org.stagemonitor.tracing.AbstractExternalRequest;
import org.stagemonitor.tracing.TracingPlugin;
import org.stagemonitor.tracing.metrics.ExternalRequestMetricsSpanEventListener;
import org.stagemonitor.tracing.profiler.Profiler;
import org.stagemonitor.util.StringUtils;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import javax.sql.DataSource;
import io.opentracing.Span;
import io.opentracing.tag.Tags;
import static org.stagemonitor.core.metrics.metrics2.MetricName.name;
public class StagemonitorJdbcEventListener extends SimpleJdbcEventListener {
private static final Logger logger = LoggerFactory.getLogger(StagemonitorJdbcEventListener.class);
private static final String DB_STATEMENT = "db.statement";
private final JdbcPlugin jdbcPlugin;
private final MetricName.MetricNameTemplate getConnectionTemplate = name("get_jdbc_connection").templateFor("url");
private final ConcurrentMap<DataSource, MetaData> dataSourceUrlMap = new ConcurrentHashMap<DataSource, MetaData>();
private CorePlugin corePlugin;
private TracingPlugin tracingPlugin;
public StagemonitorJdbcEventListener() {
this(Stagemonitor.getConfiguration());
}
public StagemonitorJdbcEventListener(ConfigurationRegistry configuration) {
this.jdbcPlugin = configuration.getConfig(JdbcPlugin.class);
tracingPlugin = configuration.getConfig(TracingPlugin.class);
corePlugin = configuration.getConfig(CorePlugin.class);
}
@Override
public void onConnectionWrapped(ConnectionInformation connectionInformation) {
final Metric2Registry metricRegistry = corePlugin.getMetricRegistry();
// at the moment stagemonitor only supports monitoring connections initiated via a DataSource
if (connectionInformation.getDataSource() instanceof DataSource && corePlugin.isInitialized()) {
DataSource dataSource = (DataSource) connectionInformation.getDataSource();
ensureUrlExistsForDataSource(dataSource, connectionInformation.getConnection());
MetaData metaData = dataSourceUrlMap.get(dataSource);
metricRegistry.timer(getConnectionTemplate.build(metaData.serviceName)).update(connectionInformation.getTimeToGetConnectionNs(), TimeUnit.NANOSECONDS);
}
}
private DataSource ensureUrlExistsForDataSource(DataSource dataSource, Connection connection) {
if (!dataSourceUrlMap.containsKey(dataSource)) {
final DatabaseMetaData metaData;
try {
metaData = connection.getMetaData();
final MetaData meta = new MetaData(metaData.getUserName(), metaData.getURL(), metaData.getDatabaseProductName());
dataSourceUrlMap.put(dataSource, meta);
} catch (SQLException e) {
logger.warn(e.getMessage(), e);
}
}
return dataSource;
}
private static class MetaData {
private final String serviceName;
private final String userName;
private final String productName;
private MetaData(String userName, String url, String productName) {
this.userName = userName;
this.productName = productName;
serviceName = userName + "@" + url;
}
}
@Override
public void onBeforeAnyExecute(StatementInformation statementInformation) {
tracingPlugin.getRequestMonitor().monitorStart(new MonitoredJdbcRequest(tracingPlugin));
}
@Override
public void onAfterAnyExecute(StatementInformation statementInformation, long timeElapsedNanos, SQLException e) {
if (!TracingUtils.getTraceContext().isEmpty()) {
final Span span = TracingUtils.getTraceContext().getCurrentSpan();
if (statementInformation.getConnectionInformation().getDataSource() instanceof DataSource && jdbcPlugin.isCollectSql()) {
MetaData metaData = dataSourceUrlMap.get(statementInformation.getConnectionInformation().getDataSource());
Tags.PEER_SERVICE.set(span, metaData.serviceName);
span.setTag("db.type", metaData.productName);
span.setTag("db.user", metaData.userName);
if (StringUtils.isNotEmpty(statementInformation.getSql())) {
String sql = getSql(statementInformation.getSql(), statementInformation.getSqlWithValues());
Profiler.addIOCall(sql, timeElapsedNanos);
span.setTag(ExternalRequestMetricsSpanEventListener.EXTERNAL_REQUEST_METHOD, sql.substring(0, sql.indexOf(' ')).toUpperCase());
span.setTag(DB_STATEMENT, sql);
}
}
tracingPlugin.getRequestMonitor().monitorStop();
}
}
private String getSql(String prepared, String sql) {
if (StringUtils.isEmpty(sql) || !jdbcPlugin.isCollectPreparedStatementParameters()) {
sql = prepared;
}
return sql.trim();
}
private static class MonitoredJdbcRequest extends AbstractExternalRequest {
private MonitoredJdbcRequest(TracingPlugin tracingPlugin) {
super(tracingPlugin.getTracer());
}
@Override
protected String getType() {
return "jdbc";
}
}
}