一.关于cat
cat是吴其敏在大众点评工作期间设计开发的。是一个实时和接近全量的监控系统,它侧重于对Java应用的监控,基本接入了美团上海侧所有核心应用。 cat可以对应用的接口,mq消息,任务,mybatis,redis等组件进行监控。本文主要介绍cat如何对sql的运行情况进行监控。
二.插件源码解析
@Intercepts({@Signature( type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class} ), @Signature( type = StatementHandler.class, method = "update", args = {Statement.class} ), @Signature( type = StatementHandler.class, method = "batch", args = {Statement.class} )})
public class CatMybatisPlugin implements Interceptor { private static final Logger logger = LoggerFactory.getLogger(CatMybatisPlugin.class);
ini
public CatMybatisPlugin() {
logger.info("CatMybatisPlugin cons...");
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
// logger.info("cat mybatis plugin invoked.....");
//获取到方法的第一个参数
Object firstArg = invocation.getArgs()[0];
Statement statement;
//判断是否为代理类
if (Proxy.isProxyClass(firstArg.getClass())) {
statement = (Statement) SystemMetaObject.forObject(firstArg).getValue("h.statement");
} else {
statement = (Statement) firstArg;
}
MetaObject stmtMetaObj = SystemMetaObject.forObject(statement);
try {
statement = (Statement) stmtMetaObj.getValue("stmt.statement");
} catch (Exception var20) {
;
}
if (stmtMetaObj.hasGetter("delegate")) {
try {
statement = (Statement) stmtMetaObj.getValue("delegate");
} catch (Exception var19) {
;
}
}
Object target = PluginUtils.realTarget(invocation.getTarget());
MetaObject metaObject = SystemMetaObject.forObject(target);
//得到statement
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
//得到sql的方法名
String methodName = this.getMethodName(mappedStatement);
Transaction t = Cat.newTransaction("SQL", methodName);
/***************sql****************/
String originalSql = null;
String stmtClassName = statement.getClass().getName();
Class clazz;
Object stmtSql;
originalSql = statement.toString();
//空白字符替换
originalSql = originalSql.replaceAll("[\\s]+", " ");
//获取sql开始的位置,原始sql带有statement类名前缀
int index = this.indexOfSqlStart(originalSql);
//截取得到完整sql
if (index > 0) {
originalSql = originalSql.substring(index);
}
/****************sql*****/
//sql命令的类型,例如insert,update等
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
//记录sql的时间
Cat.logEvent("SQL.Method", sqlCommandType.name().toLowerCase(), Message.SUCCESS, originalSql);
String url = this.getSQLDatabaseUrlByStatement(mappedStatement);
Cat.logEvent("SQL.Database", url);
//执行sql,记录sql耗时,是否异常等
return doFinish(invocation, t);
}
private int indexOfSqlStart(String sql) {
String upperCaseSql = sql.toUpperCase();
Set<Integer> set = new HashSet();
set.add(upperCaseSql.indexOf("SELECT "));
set.add(upperCaseSql.indexOf("UPDATE "));
set.add(upperCaseSql.indexOf("INSERT "));
set.add(upperCaseSql.indexOf("DELETE "));
set.remove(-1);
if (CollectionUtils.isEmpty(set)) {
return -1;
} else {
List<Integer> list = new ArrayList(set);
list.sort(Comparator.naturalOrder());
return (Integer) list.get(0);
}
}
public Method getMethodRegular(Class<?> clazz, String methodName) {
if (Object.class.equals(clazz)) {
return null;
} else {
Method[] var3 = clazz.getDeclaredMethods();
int var4 = var3.length;
for (int var5 = 0; var5 < var4; ++var5) {
Method method = var3[var5];
if (method.getName().equals(methodName)) {
return method;
}
}
return this.getMethodRegular(clazz.getSuperclass(), methodName);
}
}
private String getMethodName(MappedStatement mappedStatement) {
String[] strArr = mappedStatement.getId().split("\\.");
String methodName = strArr[strArr.length - 2] + "." + strArr[strArr.length - 1];
return methodName;
}
private Object doFinish(Invocation invocation, Transaction t) throws InvocationTargetException, IllegalAccessException {
Object returnObj = null;
try {
//执行RoutingStatementHandler中的batch,query,update方法
returnObj = invocation.proceed();
t.setStatus(Transaction.SUCCESS);
} catch (InvocationTargetException e) {
Cat.logError(e.getTargetException());
throw e;
} catch (Exception e) {
Cat.logError(e);
throw e;
} finally {
t.complete();
}
return returnObj;
}
private String getSQLDatabaseUrlByStatement(MappedStatement mappedStatement) {
String url = null;
DataSource dataSource = null;
try {
Configuration configuration = mappedStatement.getConfiguration();
Environment environment = configuration.getEnvironment();
dataSource = environment.getDataSource();
url = switchDataSource(dataSource);
return url;
} catch (NoSuchFieldException | IllegalAccessException | NullPointerException e) {
Cat.logError(e);
}
Cat.logError(new Exception("UnSupport type of DataSource : " + dataSource.getClass().toString()));
return MYSQL_DEFAULT_URL;
}
private String switchDataSource(DataSource dataSource) throws NoSuchFieldException, IllegalAccessException {
if (dataSource != null) {
switch (dataSource.getClass().getName()) {
case DruidDataSourceClassName:
return getDataSourceSqlURL(dataSource, DruidDataSourceClassName, "getUrl");
case DBCPBasicDataSourceClassName:
return getDataSourceSqlURL(dataSource, DBCPBasicDataSourceClassName, "getUrl");
case DBCP2BasicDataSourceClassName:
return getDataSourceSqlURL(dataSource, DBCP2BasicDataSourceClassName, "getUrl");
case C3P0ComboPooledDataSourceClassName:
return getDataSourceSqlURL(dataSource, C3P0ComboPooledDataSourceClassName, "getJdbcUrl");
case HikariCPDataSourceClassName:
return getDataSourceSqlURL(dataSource, HikariCPDataSourceClassName, "getJdbcUrl");
case BoneCPDataSourceClassName:
return getDataSourceSqlURL(dataSource, BoneCPDataSourceClassName, "getJdbcUrl");
default:
return dataSource.getClass().getName();
}
}
return null;
}
private String getDataSourceSqlURL(DataSource dataSource, String runtimeDataSourceClassName, String sqlURLMethodName) {
Class<?> dataSourceClass = null;
try {
dataSourceClass = Class.forName(runtimeDataSourceClassName);
} catch (ClassNotFoundException e) {
}
Method sqlURLMethod = ReflectionUtils.findMethod(dataSourceClass, sqlURLMethodName);
return (String) ReflectionUtils.invokeMethod(sqlURLMethod, dataSource);
}
@Override
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties properties) {
}
}