背景:
Cat Java 客户端支持 JDK 1.6 及以上版本。
1.客户端集成CAT
1.添加依赖
- maven项目 在pom.xml 中引入如下内容:
<dependency>
<groupId>com.dianping.cat</groupId>
<artifactId>cat-client</artifactId>
<version>3.0.0</version>
</dependency>
- 非maven项目 直接引入jar包
如果没有使用maven管理依赖,可以直接复制 jar/cat-client-3.0.0.jar 到项目 WEB_INF/lib 路径下。jar包下载地址:
https://github.com/dianping/cat/blob/master/lib/java/jar/cat-client-3.0.0.jar
2.添加过滤器
- springboot项目(WebConfig.java)
package com.gdie.cat.config;
import com.dianping.cat.servlet.CatFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @Description TODO
* @Author jiuwei
* @Date Created in 下午11:53 2020/4/2
* @Version 1.0
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
/**
* cat的埋点过滤器
*
* @return
*/
@Bean
public FilterRegistrationBean catFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean();
CatFilter filter = new CatFilter();
registration.setFilter(filter);
registration.addUrlPatterns("/*");
registration.setName("cat-filter");
registration.setOrder(1);
return registration;
}
}
- springmvc项目(web.xml)
<filter>
<filter-name>cat-filter</filter-name>
<filter-class>com.dianping.cat.servlet.CatFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>cat-filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3.配置/data/appdatas/cat/client.xml
vi /data/appdatas/cat/client.xml
内容如下:
<?xml version="1.0" encoding="utf-8"?>
<config mode="client">
<servers>
<server ip="192.168.1.102" port="2280" http-port="8080"/>
</servers>
</config>
4.在项目中创建app.properties
然后你需要在你的项目中创建 src/main/resources/META-INF/app.properties
文件, 并添加如下内容:
app.name={appKey}
- 说明
- appkey 只能包含英文字母 (a-z, A-Z)、数字 (0-9)、下划线 (_) 和中划线 (-)。
- 现在java的cat client会自动懒加载,已经没有必要手动初始化客户端。
查看cat是否有监控到集成的客户端。
2.mybatis集成CAT
1.引入拦截器
将CatMybatisPlugin.java 放到任意SpringBoot 能扫描到的package下面
package com.gdie.cat.plugin;
import com.dianping.cat.Cat;
import com.dianping.cat.message.Message;
import com.dianping.cat.message.Transaction;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.mybatis.spring.transaction.SpringManagedTransaction;
import org.springframework.util.ReflectionUtils;
import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
/**
* @Description 对MyBatis进行拦截,添加Cat监控
* @Author jiuwei
* @Date Created in 下午11:53 2020/4/2
* @Version 1.0
*/
@Intercepts({
@Signature(method = "query", type = Executor.class, args = {
MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class}),
@Signature(method = "update", type = Executor.class, args = {MappedStatement.class, Object.class})
})
public class CatMybatisPlugin implements Interceptor {
private static Log logger = LogFactory.getLog(CatMybatisPlugin.class);
//缓存,提高性能
private static final Map<String, String> sqlURLCache = new ConcurrentHashMap<String, String>(256);
private static final String EMPTY_CONNECTION = "jdbc:mysql://unknown:3306/%s?useUnicode=true";
private Executor target;
// druid 数据源的类名称
private static final String DruidDataSourceClassName = "com.alibaba.druid.pool.DruidDataSource";
// dbcp 数据源的类名称
private static final String DBCPBasicDataSourceClassName = "org.apache.commons.dbcp.BasicDataSource";
// dbcp2 数据源的类名称
private static final String DBCP2BasicDataSourceClassName = "org.apache.commons.dbcp2.BasicDataSource";
// c3p0 数据源的类名称
private static final String C3P0ComboPooledDataSourceClassName = "com.mchange.v2.c3p0.ComboPooledDataSource";
// HikariCP 数据源的类名称
private static final String HikariCPDataSourceClassName = "com.zaxxer.hikari.HikariDataSource";
// BoneCP 数据源的类名称
private static final String BoneCPDataSourceClassName = "com.jolbox.bonecp.BoneCPDataSource";
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
//得到类名,方法
String[] strArr = mappedStatement.getId().split("\\.");
String methodName = strArr[strArr.length - 2] + "." + strArr[strArr.length - 1];
Transaction t = Cat.newTransaction("SQL", methodName);
//得到sql语句
Object parameter = null;
if (invocation.getArgs().length > 1) {
parameter = invocation.getArgs()[1];
}
BoundSql boundSql = mappedStatement.getBoundSql(parameter);
Configuration configuration = mappedStatement.getConfiguration();
String sql = showSql(configuration, boundSql);
//获取SQL类型
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
Cat.logEvent("SQL.Method", sqlCommandType.name().toLowerCase(), Message.SUCCESS, sql);
String s = this.getSQLDatabase();
Cat.logEvent("SQL.Database", s);
Object returnObj = null;
try {
returnObj = invocation.proceed();
t.setStatus(Transaction.SUCCESS);
} catch (Exception e) {
Cat.logError(e);
} finally {
t.complete();
}
return returnObj;
}
private DataSource getDataSource() {
org.apache.ibatis.transaction.Transaction transaction = this.target.getTransaction();
if (transaction == null) {
logger.error(String.format("Could not find transaction on target [%s]", this.target));
return null;
}
if (transaction instanceof SpringManagedTransaction) {
String fieldName = "dataSource";
Field field = ReflectionUtils.findField(transaction.getClass(), fieldName, DataSource.class);
if (field == null) {
logger.error(String.format("Could not find field [%s] of type [%s] on target [%s]",
fieldName, DataSource.class, this.target));
return null;
}
ReflectionUtils.makeAccessible(field);
DataSource dataSource = (DataSource) ReflectionUtils.getField(field, transaction);
return dataSource;
}
logger.error(String.format("---the transaction is not SpringManagedTransaction:%s", transaction.getClass().toString()));
return null;
}
/**
* 重写 getSqlURL 方法
*
* @author fanlychie (https://github.com/fanlychie)
*/
private String getSqlURL() {
// 客户端使用的数据源
DataSource dataSource = this.getDataSource();
if (dataSource != null) {
// 处理常见的数据源
switch (dataSource.getClass().getName()) {
// druid
case DruidDataSourceClassName:
return getDataSourceSqlURL(dataSource, DruidDataSourceClassName, "getUrl");
// dbcp
case DBCPBasicDataSourceClassName:
return getDataSourceSqlURL(dataSource, DBCPBasicDataSourceClassName, "getUrl");
// dbcp2
case DBCP2BasicDataSourceClassName:
return getDataSourceSqlURL(dataSource, DBCP2BasicDataSourceClassName, "getUrl");
// c3p0
case C3P0ComboPooledDataSourceClassName:
return getDataSourceSqlURL(dataSource, C3P0ComboPooledDataSourceClassName, "getJdbcUrl");
// HikariCP
case HikariCPDataSourceClassName:
return getDataSourceSqlURL(dataSource, HikariCPDataSourceClassName, "getJdbcUrl");
// BoneCP
case BoneCPDataSourceClassName:
return getDataSourceSqlURL(dataSource, BoneCPDataSourceClassName, "getJdbcUrl");
}
}
return null;
}
/**
* 获取数据源的SQL地址
*
* @param dataSource 数据源
* @param runtimeDataSourceClassName 运行时真实的数据源的类名称
* @param sqlURLMethodName 获取SQL地址的方法名称
* @author fanlychie (https://github.com/fanlychie)
*/
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);
}
private String getSQLDatabase() {
// String dbName = RouteDataSourceContext.getRouteKey();
String dbName = null; //根据设置的多数据源修改此处,获取dbname
if (dbName == null) {
dbName = "DEFAULT";
}
String url = CatMybatisPlugin.sqlURLCache.get(dbName);
if (url != null) {
return url;
}
url = this.getSqlURL();//目前监控只支持mysql ,其余数据库需要各自修改监控服务端
if (url == null) {
url = String.format(EMPTY_CONNECTION, dbName);
}
CatMybatisPlugin.sqlURLCache.put(dbName, url);
return url;
}
/**
* 解析sql语句
*
* @param configuration
* @param boundSql
* @return
*/
public String showSql(Configuration configuration, BoundSql boundSql) {
Object parameterObject = boundSql.getParameterObject();
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
if (parameterMappings.size() > 0 && parameterObject != null) {
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(parameterObject)));
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
for (ParameterMapping parameterMapping : parameterMappings) {
String propertyName = parameterMapping.getProperty();
if (metaObject.hasGetter(propertyName)) {
Object obj = metaObject.getValue(propertyName);
sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
} else if (boundSql.hasAdditionalParameter(propertyName)) {
Object obj = boundSql.getAdditionalParameter(propertyName);
sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
}
}
}
}
return sql;
}
/**
* 参数解析
*
* @param obj
* @return
*/
private String getParameterValue(Object obj) {
String value = null;
if (obj instanceof String) {
value = "'" + obj.toString() + "'";
} else if (obj instanceof Date) {
DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
value = "'" + formatter.format(new Date()) + "'";
} else {
if (obj != null) {
value = obj.toString();
} else {
value = "";
}
}
return value;
}
@Override
public Object plugin(Object target) {
if (target instanceof Executor) {
this.target = (Executor) target;
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties properties) {
}
}
2.数据源配置
package com.gdie.cat.config;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.gdie.cat.plugin.CatMybatisPlugin;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* @Description 单数据源配置
* @Author jiuwei
* @Date Created in 下午11:53 2020/4/2
* @Version 1.0
*/
@Configuration
@EnableTransactionManagement
@MapperScan(basePackages = {"**.mapper"})
public class SingleDataSourceConfig {
/**
* mybatisplus的全局配置
*
* @return
*/
@Bean
public GlobalConfig globalConfiguration() {
GlobalConfig conf = new GlobalConfig();
return conf;
}
/**
* 分页插件
*
* @return
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor page = new PaginationInterceptor();
page.setDialectType("mysql");// 指定数据库类型
return page;
}
@Bean
public CatMybatisPlugin catMybatisPlugin() {
return new CatMybatisPlugin();
}
}
在cat上就可以调用接口时sql的情况