CAT
CAT安装
1. 下载 git clone https://github.com/dianping/cat.git
2. 打包 mvn clean install -DskipTests
打包好的文件在 cat\cat-home\target\cat-home.war , 改名为 cat.war 就好了。
3. 创建好文件夹,此目录会存储CAT的数据和配置文件
mkdir /data
mkdir -p /data/appdatas/cat
mkdir -p /data/applogs/cat
cd /data/appdatas/cat
4. 配置 client.xml。 vim client.xml。(我们这里虽然是服务端,但是它自己也是一个客户端。我们在这里指定服务端地址 我这里是(192.168.200.100:8080)
<?xml version="1.0" encoding="utf-8"?> <config mode="client"> <servers> <!--<server ip="192.168.1.101" port="2280" http-port="8080"/>--> <server ip="192.168.200.100" port="2280" http-port="8080"/> </servers> </config>
配置 datasources.xml。 vim datasources.xml (指定数据库配置)
<?xml version="1.0" encoding="utf-8"?> <data-sources> <data-source id="cat"> <maximum-pool-size>3</maximum-pool-size> <connection-timeout>1s</connection-timeout> <idle-timeout>10m</idle-timeout> <statement-cache-size>1000</statement-cache-size> <properties> <driver>com.mysql.jdbc.Driver</driver> <url><![CDATA[jdbc:mysql://192.168.200.100:3306/cat]]></url> <user>root</user> <password>123456</password> <connectionProperties><![CDATA[useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&socketTimeout=120000]]></connectionProperties> </properties> </data-source> </data-sources>
5. 配置权限 chmod -R 777 /data
6. 创建数据库
CREATE DATABASE `cat` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_bin'; use `cat`; CREATE TABLE `dailyreport` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL COMMENT '报表名称, transaction, problem...', `ip` varchar(50) NOT NULL COMMENT '报表来自于哪台cat-consumer机器', `domain` varchar(50) NOT NULL COMMENT '报表处理的Domain信息', `period` datetime NOT NULL COMMENT '报表时间段', `type` tinyint(4) NOT NULL COMMENT '报表数据格式, 1/xml, 2/json, 默认1', `creation_date` datetime NOT NULL COMMENT '报表创建时间', PRIMARY KEY (`id`), UNIQUE KEY `period` (`period`,`domain`,`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='天报表'; CREATE TABLE `weeklyreport` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL COMMENT '报表名称, transaction, problem...', `ip` varchar(50) NOT NULL COMMENT '报表来自于哪台cat-consumer机器', `domain` varchar(50) NOT NULL COMMENT '报表处理的Domain信息', `period` datetime NOT NULL COMMENT '报表时间段', `type` tinyint(4) NOT NULL COMMENT '报表数据格式, 1/xml, 2/json, 默认1', `creation_date` datetime NOT NULL COMMENT '报表创建时间', PRIMARY KEY (`id`), UNIQUE KEY `period` (`period`,`domain`,`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='周报表'; CREATE TABLE `monthreport` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL COMMENT '报表名称, transaction, problem...', `ip` varchar(50) NOT NULL COMMENT '报表来自于哪台cat-consumer机器', `domain` varchar(50) NOT NULL COMMENT '报表处理的Domain信息', `period` datetime NOT NULL COMMENT '报表时间段', `type` tinyint(4) NOT NULL COMMENT '报表数据格式, 1/xml, 2/json, 默认1', `creation_date` datetime NOT NULL COMMENT '报表创建时间', PRIMARY KEY (`id`), UNIQUE KEY `period` (`period`,`domain`,`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='月报表'; CREATE TABLE `hostinfo` ( `id` int(11) NOT NULL AUTO_INCREMENT, `ip` varchar(50) NOT NULL COMMENT '部署机器IP', `domain` varchar(200) NOT NULL COMMENT '部署机器对应的项目名', `hostname` varchar(200) DEFAULT NULL COMMENT '机器域名', `creation_date` datetime NOT NULL, `last_modified_date` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ip_index` (`ip`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='IP和项目名的对应关系'; CREATE TABLE `hourlyreport` ( `id` int(11) NOT NULL AUTO_INCREMENT, `type` tinyint(4) NOT NULL COMMENT '报表类型, 1/xml, 9/binary 默认1', `name` varchar(20) NOT NULL COMMENT '报表名称', `ip` varchar(50) DEFAULT NULL COMMENT '报表来自于哪台机器', `domain` varchar(50) NOT NULL COMMENT '报表项目', `period` datetime NOT NULL COMMENT '报表时间段', `creation_date` datetime NOT NULL COMMENT '报表创建时间', PRIMARY KEY (`id`), KEY `IX_Domain_Name_Period` (`domain`,`name`,`period`), KEY `IX_Name_Period` (`name`,`period`), KEY `IX_Period` (`period`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPRESSED COMMENT='用于存放实时报表信息,处理之后的结果'; CREATE TABLE `hourly_report_content` ( `report_id` int(11) NOT NULL COMMENT '报表ID', `content` longblob NOT NULL COMMENT '二进制报表内容', `period` datetime NOT NULL COMMENT '报表时间段', `creation_date` datetime NOT NULL COMMENT '创建时间', PRIMARY KEY (`report_id`), KEY `IX_Period` (`period`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPRESSED COMMENT='小时报表二进制内容'; CREATE TABLE `daily_report_content` ( `report_id` int(11) NOT NULL COMMENT '报表ID', `content` longblob NOT NULL COMMENT '二进制报表内容', `period` datetime COMMENT '报表时间段', `creation_date` datetime NOT NULL COMMENT '创建时间', PRIMARY KEY (`report_id`), KEY `IX_Period` (`period`) )ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPRESSED COMMENT='天报表二进制内容'; CREATE TABLE `weekly_report_content` ( `report_id` int(11) NOT NULL COMMENT '报表ID', `content` longblob NOT NULL COMMENT '二进制报表内容', `period` datetime COMMENT '报表时间段', `creation_date` datetime NOT NULL COMMENT '创建时间', PRIMARY KEY (`report_id`), KEY `IX_Period` (`period`) )ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPRESSED COMMENT='周报表二进制内容'; CREATE TABLE `monthly_report_content` ( `report_id` int(11) NOT NULL COMMENT '报表ID', `content` longblob NOT NULL COMMENT '二进制报表内容', `period` datetime COMMENT '报表时间段', `creation_date` datetime NOT NULL COMMENT '创建时间', PRIMARY KEY (`report_id`), KEY `IX_Period` (`period`) )ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPRESSED COMMENT='月报表二进制内容'; CREATE TABLE `businessReport` ( `id` int(11) NOT NULL AUTO_INCREMENT, `type` tinyint(4) NOT NULL COMMENT '报表类型 报表数据格式, 1/Binary, 2/xml , 3/json', `name` varchar(20) NOT NULL COMMENT '报表名称', `ip` varchar(50) NOT NULL COMMENT '报表来自于哪台机器', `productLine` varchar(50) NOT NULL COMMENT '指标来源于哪个产品组', `period` datetime NOT NULL COMMENT '报表时间段', `content` longblob COMMENT '用于存放报表的具体内容', `creation_date` datetime NOT NULL COMMENT '报表创建时间', PRIMARY KEY (`id`), KEY `IX_Period_productLine_name` (`period`,`productLine`,`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPRESSED COMMENT='用于存放业务监控实时报表信息,处理之后的结果'; CREATE TABLE `task` ( `id` int(11) NOT NULL AUTO_INCREMENT, `producer` varchar(20) NOT NULL COMMENT '任务创建者ip', `consumer` varchar(20) NULL COMMENT '任务执行者ip', `failure_count` tinyint(4) NOT NULL COMMENT '任务失败次数', `report_name` varchar(20) NOT NULL COMMENT '报表名称, transaction, problem...', `report_domain` varchar(50) NOT NULL COMMENT '报表处理的Domain信息', `report_period` datetime NOT NULL COMMENT '报表时间', `status` tinyint(4) NOT NULL COMMENT '执行状态: 1/todo, 2/doing, 3/done 4/failed', `task_type` tinyint(4) NOT NULL DEFAULT '1' COMMENT '0表示小时任务,1表示天任务', `creation_date` datetime NOT NULL COMMENT '任务创建时间', `start_date` datetime NULL COMMENT '开始时间, 这次执行开始时间', `end_date` datetime NULL COMMENT '结束时间, 这次执行结束时间', PRIMARY KEY (`id`), UNIQUE KEY `task_period_domain_name_type` (`report_period`,`report_domain`,`report_name`,`task_type`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='后台任务'; CREATE TABLE `project` ( `id` int(11) NOT NULL AUTO_INCREMENT, `domain` varchar(200) NOT NULL COMMENT '项目名称', `cmdb_domain` varchar(200) DEFAULT NULL COMMENT 'cmdb项目名称', `level` int(5) DEFAULT NULL COMMENT '项目级别', `bu` varchar(50) DEFAULT NULL COMMENT 'CMDB事业部', `cmdb_productline` varchar(50) DEFAULT NULL COMMENT 'CMDB产品线', `owner` varchar(50) DEFAULT NULL COMMENT '项目负责人', `email` longtext DEFAULT NULL COMMENT '项目组邮件', `phone` longtext DEFAULT NULL COMMENT '联系电话', `creation_date` datetime DEFAULT NULL COMMENT '创建时间', `modify_date` datetime DEFAULT NULL COMMENT '修改时间', PRIMARY KEY (`id`), UNIQUE KEY `domain` (`domain`) )ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='项目基本信息'; CREATE TABLE `topologyGraph` ( `id` int(11) NOT NULL AUTO_INCREMENT, `ip` varchar(50) NOT NULL COMMENT '报表来自于哪台cat-client机器ip', `period` datetime NOT NULL COMMENT '报表时间段,精确到分钟', `type` tinyint(4) NOT NULL COMMENT '报表数据格式, 1/xml, 2/json, 3/binary', `content` longblob COMMENT '用于存放报表的具体内容', `creation_date` datetime NOT NULL COMMENT '报表创建时间', PRIMARY KEY (`id`), KEY `period` (`period`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用于存储历史的拓扑图曲线'; CREATE TABLE `config` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL COMMENT '配置名称', `content` longtext COMMENT '配置的具体内容', `creation_date` datetime NOT NULL COMMENT '配置创建时间', `modify_date` datetime NOT NULL COMMENT '配置修改时间', PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用于存储系统的全局配置信息'; CREATE TABLE `baseline` ( `id` int(11) NOT NULL AUTO_INCREMENT, `report_name` varchar(100) DEFAULT NULL, `index_key` varchar(100) DEFAULT NULL, `report_period` datetime DEFAULT NULL, `data` blob, `creation_date` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `period_name_key` (`report_period`,`report_name`,`index_key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `alteration` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增长ID', `type` varchar(64) NOT NULL COMMENT '分类', `title` varchar(128) NOT NULL COMMENT '变更标题', `domain` varchar(128) NOT NULL COMMENT '变更项目', `hostname` varchar(128) NOT NULL COMMENT '变更机器名', `ip` varchar(128) DEFAULT NULL COMMENT '变更机器IP', `date` datetime NOT NULL COMMENT '变更时间', `user` varchar(45) NOT NULL COMMENT '变更用户', `alt_group` varchar(45) DEFAULT NULL COMMENT '变更组别', `content` longtext NOT NULL COMMENT '变更内容', `url` varchar(200) DEFAULT NULL COMMENT '变更链接', `status` tinyint(4) DEFAULT '0' COMMENT '变更状态', `creation_date` datetime NOT NULL COMMENT '数据库创建时间', PRIMARY KEY (`id`), KEY `ind_date_domain_host` (`date`,`domain`,`hostname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='变更表'; CREATE TABLE `alert` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增长ID', `domain` varchar(128) NOT NULL COMMENT '告警项目', `alert_time` datetime NOT NULL COMMENT '告警时间', `category` varchar(64) NOT NULL COMMENT '告警分类:network/business/system/exception -alert', `type` varchar(64) NOT NULL COMMENT '告警类型:error/warning', `content` longtext NOT NULL COMMENT '告警内容', `metric` varchar(128) NOT NULL COMMENT '告警指标', `creation_date` datetime NOT NULL COMMENT '数据插入时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='存储告警信息'; CREATE TABLE `alert_summary` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增长ID', `domain` varchar(128) NOT NULL COMMENT '告警项目', `alert_time` datetime NOT NULL COMMENT '告警时间', `content` longtext NOT NULL COMMENT '统一告警内容', `creation_date` datetime NOT NULL COMMENT '数据插入时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='统一告警信息'; CREATE TABLE `operation` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增长ID', `user` varchar(128) NOT NULL COMMENT '用户名', `module` varchar(128) NOT NULL COMMENT '模块', `operation` varchar(128) NOT NULL COMMENT '操作', `time` datetime NOT NULL COMMENT '修改时间', `content` longtext NOT NULL COMMENT '修改内容', `creation_date` datetime NOT NULL COMMENT '数据插入时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户操作日志'; CREATE TABLE `overload` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增长ID', `report_id` int(11) NOT NULL COMMENT '报告id', `report_type` tinyint(4) NOT NULL COMMENT '报告类型 1:hourly 2:daily 3:weekly 4:monthly', `report_size` double NOT NULL COMMENT '报告大小 单位MB', `period` datetime NOT NULL COMMENT '报表时间', `creation_date` datetime NOT NULL COMMENT '创建时间', PRIMARY KEY (`id`), KEY `period` (`period`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='过大容量表'; CREATE TABLE `config_modification` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增长ID', `user_name` varchar(64) NOT NULL COMMENT '用户名', `account_name` varchar(64) NOT NULL COMMENT '账户名', `action_name` varchar(64) NOT NULL COMMENT 'action名', `argument` longtext COMMENT '参数内容', `date` datetime NOT NULL COMMENT '修改时间', `creation_date` datetime NOT NULL COMMENT '创建时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='配置修改记录表'; CREATE TABLE `user_define_rule` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增长ID', `content` text NOT NULL COMMENT '用户定义规则', `creation_date` datetime NOT NULL COMMENT '创建时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户定义规则表'; CREATE TABLE `business_config` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL DEFAULT '' COMMENT '配置名称', `domain` varchar(50) NOT NULL DEFAULT '' COMMENT '项目', `content` longtext COMMENT '配置内容', `updatetime` datetime NOT NULL, PRIMARY KEY (`id`), KEY `updatetime` (`updatetime`), KEY `name_domain` (`name`,`domain`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `metric_screen` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL COMMENT '配置名称', `graph_name` varchar(50) NOT NULL DEFAULT '' COMMENT 'Graph名称', `view` varchar(50) NOT NULL DEFAULT '' COMMENT '视角', `endPoints` longtext NOT NULL, `measurements` longtext NOT NULL COMMENT '配置的指标', `content` longtext NOT NULL COMMENT '配置的具体内容', `creation_date` datetime NOT NULL COMMENT '配置创建时间', `updatetime` datetime NOT NULL COMMENT '配置修改时间', PRIMARY KEY (`id`), UNIQUE KEY `name_graph` (`name`,`graph_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='系统监控的screen配置'; CREATE TABLE `metric_graph` ( `id` int(11) NOT NULL AUTO_INCREMENT, `graph_id` int(11) NOT NULL COMMENT '大盘ID', `name` varchar(50) NOT NULL COMMENT '配置ID', `content` longtext COMMENT '配置的具体内容', `creation_date` datetime NOT NULL COMMENT '配置创建时间', `updatetime` datetime NOT NULL COMMENT '配置修改时间', PRIMARY KEY (`id`), UNIQUE `name` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='系统监控的graph配置'; CREATE TABLE `server_alarm_rule` ( `id` int(11) NOT NULL AUTO_INCREMENT, `category` varchar(50) NOT NULL COMMENT '监控分类', `endPoint` varchar(200) NOT NULL COMMENT '监控对象ID', `measurement` varchar(200) NOT NULL COMMENT '监控指标', `tags` varchar(200) NOT NULL DEFAULT '' COMMENT '监控指标标签', `content` longtext NOT NULL COMMENT '配置的具体内容', `type` varchar(20) NOT NULL DEFAULT '' COMMENT '数据聚合方式', `creator` varchar(100) DEFAULT '' COMMENT '创建人', `creation_date` datetime NOT NULL COMMENT '配置创建时间', `updatetime` datetime NOT NULL COMMENT '配置修改时间', PRIMARY KEY (`id`), KEY `updatetime` (`updatetime`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='系统告警的配置';
7. 将 cat.war 放到 tomcat/webapps/ 目录下,然后启动tomcat
192.168.200.100:8080/cat 此时会显示“出问题CAT服务端”。
8. http://192.168.200.100:8080/cat/s/config?op=serverConfigUpdate 修改下服务器配置, 直接点击 “提交 重算路由” 就好了。然后进入首页就显示正常了。然后重启tomcat使客户端路由生效。
admin/admin
<?xml version="1.0" encoding="utf-8"?> <server-config> <server id="default"> <properties> <property name="local-mode" value="false"/> <property name="job-machine" value="false"/> <property name="send-machine" value="false"/> <property name="alarm-machine" value="false"/> <property name="hdfs-enabled" value="false"/> <property name="remote-servers" value="192.168.200.100:8080"/> </properties> <storage local-base-dir="/data/appdatas/cat/bucket/" max-hdfs-storage-time="15" local-report-storage-time="2" local-logivew-storage-time="1" har-mode="true" upload-thread="5"> <hdfs id="dump" max-size="128M" server-uri="hdfs://127.0.0.1/" base-dir="/user/cat/dump"/> <harfs id="dump" max-size="128M" server-uri="har://127.0.0.1/" base-dir="/user/cat/dump"/> <properties> <property name="hadoop.security.authentication" value="false"/> <property name="dfs.namenode.kerberos.principal" value="hadoop/dev80.hadoop@testserver.com"/> <property name="dfs.cat.kerberos.principal" value="cat@testserver.com"/> <property name="dfs.cat.keytab.file" value="/data/appdatas/cat/cat.keytab"/> <property name="java.security.krb5.realm" value="value1"/> <property name="java.security.krb5.kdc" value="value2"/> </properties> </storage> <consumer> <long-config default-url-threshold="1000" default-sql-threshold="100" default-service-threshold="50"> <domain name="cat" url-threshold="500" sql-threshold="500"/> <domain name="OpenPlatformWeb" url-threshold="100" sql-threshold="500"/> </long-config> </consumer> </server> <server id="192.168.200.100"> <properties> <property name="job-machine" value="true"/> <property name="send-machine" value="true"/> <property name="alarm-machine" value="true"/> </properties> </server> </server-config>
<?xml version="1.0" encoding="utf-8"?> <router-config backup-server="192.168.200.100" backup-server-port="2280"> <default-server id="192.168.200.100" weight="1.0" port="2280" enable="true"/> <network-policy id="default" title="默认" block="false" server-group="default_group"> </network-policy> <server-group id="default_group" title="default-group"> <group-server id="192.168.200.100"/> </server-group> <domain id="cat"> <group id="default"> <server id="192.168.200.100" port="2280" weight="1.0"/> </group> </domain> </router-config>
客户端集成CAT
1. 导入依赖,依赖需要自己安装到仓库。
mvn install:install-file -Dfile=cat-client-3.0.0.jar -DgroupId=com.dianping.cat -DartifactId=cat-client -Dversion=3.0.0 -Dpackaging=jar <dependency> <groupId>com.dianping.cat</groupId> <artifactId>cat-client</artifactId> <version>3.0.0</version> </dependency>
2. 创建好 data/{appdatas | applogs}/cat 文件夹,同时复制 client.xml 配置文件过来。
3. 创建 src/main/resources/META-INF/app.properties 文件,配置 app.name={appkey} , appkey 只能包含英文字母 (a-z, A-Z)、数字 (0-9)、下划线 (_) 和中划线 (-)。
4. 编写demo,然后访问这个接口。此时我们看本地的日志,说是连接不到服务器,但是只要最后一行提示初始化完成就好了。就可以看到我们CAT大盘的服务信息了。如果没有看到,1. 检查本地的client.xml是否配置错误。 2. data目录是否有读写权限(其实能看到本地日志就是有权限的)。3. 服务端提交 重算路由后重启tomcat。4. 服务器和本地时间不同步。
import com.alibaba.fastjson.JSON; import com.dianping.cat.Cat; import com.dianping.cat.message.Event; import com.dianping.cat.message.Transaction; import com.wulei.common.utils.IPHelper; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.net.URLDecoder; import java.util.Arrays; /** * @title: 日志链路绑定 * @author: jay.wu * @date: 2022/6/15 10:32 */ @Aspect @Component @Slf4j public class CatAspect { private static final String METHOD_GET = "GET"; private static final String METHOD_POST = "POST"; private static final String CAT_NAME = "URL"; public static ThreadLocal<Transaction> threadLocal = new ThreadLocal<>(); // 扫描这个包下所有类的方法 public * com.wulei.web..*.*(..)) 标识扫描web包下以及所有子包方法 //@Pointcut("execution(* com.shxt.core.*Service.add(int,int))||execution(* com.shxt.core.*Service.sub(int,int))") @Pointcut("execution(public * com.wulei.web..*.*(..)) ") public void webLog() { } /** * 环绕通知 * @param joinPoint * @return * @throws Throwable */ @Around("webLog()") public Object around(ProceedingJoinPoint joinPoint)throws Throwable { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); String url = request.getRequestURL().toString(); // 开启第一个Transaction,类别为URL Transaction t = Cat.newTransaction(CAT_NAME,url); // 设置开始时间 t.setTimestamp(System.currentTimeMillis()); //添加额外的数据, 如果有多个addData(),会自动&拼接这些数据 t.addData("param", getParam(joinPoint)); threadLocal.set(t); // 参数1:type类型 参数2:名称 参数3:状态 参数4:打印信息字符串 Cat.logEvent(CAT_NAME+".Server","serverIp", Event.SUCCESS,"ip="+IPHelper.getServerIp()); // 用于记录业务指标、指标可能包含对一个指标记录次数、记录平均值、记录总和,业务指标最低统计粒度为1分钟。 // 每次累加1(每分钟为一个统计维度) Cat.logMetricForCount("metric.key"); // Cat.logMetricForCount("metric.key", 5);每次累加5 // 求平均值 //Cat.logMetricForDuration("metric.key",5); try { // 执行业务方法 Object obj = joinPoint.proceed(); // 成功执行Transaction t.setStatus(Transaction.SUCCESS); return obj; }finally { // 修改执行耗时 t.setDurationInMillis(System.currentTimeMillis()-t.getTimestamp()); // 结束这一Transaction t.complete(); } } /** * 异常通知 */ @AfterThrowing(pointcut = "webLog()", throwing = "e") public void afterThrowing(Throwable e) { Transaction t = threadLocal.get(); // 失败,设置异常 t.setStatus(e); // 打印日志到CAT, 可以在EVent和Dashboard看到堆栈 Cat.logError(e); // 记录日志到CAT, 可以在 Transaction 的 show 里面直接看堆栈 Cat.logEvent(CAT_NAME+".Server","errorStack", Event.SUCCESS,"Exception ["+e.getCause()+" - "+e.getMessage()+"] stackTrace["+Arrays.deepToString(e.getStackTrace())+"]"); } private String getParam(ProceedingJoinPoint joinPoint) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); try { String method = request.getMethod(); if(method.equals(METHOD_GET)){ String queryString = request.getQueryString(); if(queryString!=null){ return URLDecoder.decode(queryString, "utf-8"); } }else if(method.equals(METHOD_POST)) { return JSON.toJSONString(joinPoint.getArgs()); } }catch (Exception e){ log.error("[获取参数异常 [{} - {}] stackTrace[{}]", e.getCause(), e.getMessage(), Arrays.deepToString(e.getStackTrace())); } return null; } }
控制面板
Dashboard
Dashboard报表展示了每个分钟为维度的异常信息、同时还会把附近一段时间的异常也展示出来。这里的服务名就是 META-INF/app.properties 里面配置的那个。
例如 15:12分wulei-shop服务出现了1个异常 15:11分wulei-sjop出现了2个异常。我们点击异常的服务可以看到异常详细信息。
接口地址,可以查看log日志,以文本或树状图展示 请求参数和调用链路和时长。这里面的调用链路名称,就是代码里面创建的 Transaction。
点击 show 可以看到报错的统计图(报错的服务器和时间)
Transaction报表
查看所有请求记录
CAT监控mysql
import com.dianping.cat.Cat; import com.dianping.cat.message.Message; import com.dianping.cat.message.Transaction; import com.zaxxer.hikari.HikariDataSource; import org.apache.ibatis.datasource.pooled.PooledDataSource; import org.apache.ibatis.datasource.unpooled.UnpooledDataSource; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.*; 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 javax.sql.DataSource; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.text.DateFormat; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @title: * @author: jay.wu * @date: 2022/7/15 16:18 */ @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 final Pattern PARAMETER_PATTERN = Pattern.compile("\\?"); private static final String MYSQL_DEFAULT_URL = "jdbc:mysql://172.16.0.38:3308/yami_shops?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&autoReconnect=true"; private Executor target; @Override public Object intercept(Invocation invocation) throws Throwable { MappedStatement mappedStatement = this.getStatement(invocation); String methodName = this.getMethodName(mappedStatement); Transaction t = Cat.newTransaction("SQL", methodName); String sql = this.getSql(invocation,mappedStatement); SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType(); Cat.logEvent("SQL.Method", sqlCommandType.name().toLowerCase(), Message.SUCCESS, sql); String url = this.getSQLDatabaseUrlByStatement(mappedStatement); Cat.logEvent("SQL.Database", url); return doFinish(invocation,t); } private MappedStatement getStatement(Invocation invocation) { return (MappedStatement)invocation.getArgs()[0]; } private String getMethodName(MappedStatement mappedStatement) { String[] strArr = mappedStatement.getId().split("\\."); String methodName = strArr[strArr.length - 2] + "." + strArr[strArr.length - 1]; return methodName; } private String getSql(Invocation invocation, MappedStatement mappedStatement) { Object parameter = null; if(invocation.getArgs().length > 1){ parameter = invocation.getArgs()[1]; } BoundSql boundSql = mappedStatement.getBoundSql(parameter); Configuration configuration = mappedStatement.getConfiguration(); String sql = sqlResolve(configuration, boundSql); return sql; } private Object doFinish(Invocation invocation,Transaction t) throws InvocationTargetException, IllegalAccessException { Object returnObj = null; try { returnObj = invocation.proceed(); t.setStatus(Transaction.SUCCESS); } 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 { String url = null; if(dataSource instanceof HikariDataSource) { url = ((HikariDataSource) dataSource).getJdbcUrl(); }else if(dataSource instanceof PooledDataSource) { Field dataSource1 = dataSource.getClass().getDeclaredField("dataSource"); dataSource1.setAccessible(true); UnpooledDataSource dataSource2 = (UnpooledDataSource)dataSource1.get(dataSource); url =dataSource2.getUrl(); }else { //other dataSource expand } return url; } public String sqlResolve(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(resolveParameterValue(parameterObject))); } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); Matcher matcher = PARAMETER_PATTERN.matcher(sql); StringBuffer sqlBuffer = new StringBuffer(); for (ParameterMapping parameterMapping : parameterMappings) { String propertyName = parameterMapping.getProperty(); Object obj = null; if (metaObject.hasGetter(propertyName)) { obj = metaObject.getValue(propertyName); } else if (boundSql.hasAdditionalParameter(propertyName)) { obj = boundSql.getAdditionalParameter(propertyName); } if (matcher.find()) { matcher.appendReplacement(sqlBuffer, Matcher.quoteReplacement(resolveParameterValue(obj))); } } matcher.appendTail(sqlBuffer); sql = sqlBuffer.toString(); } } return sql; } private String resolveParameterValue(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((Date) obj) + "'"; } 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) { } }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <plugins> <plugin interceptor="com.wulei.common.CatMybatisPlugin"> </plugin> </plugins> </configuration>
这里指定配置文件必须通过xml的方式,用 @Configuration 来标识会报错,用 @Component 只能监控写请求不能监控查询请求。
告警通知
1. 服务端打开告警 http://192.168.200.100/cat/s/config?op=serverConfigUpdate
2. 配置项目的告警信息。策略是针对异常(Exception)的,很多文章配置的是 Transcation,那个不是异常的告警。我们的appkey=bbbb,当告警级别为error时,发送渠道为邮件、短信、微信,连续告警之间的间隔为1分钟(1分钟内不会重复发送)。
3. 配置告警地址,当出现异常的时候就通知到这个地址,同时会把“默认告警人”信息发送到我们地址。请求方式是post,返回200表示成功推送。
告警接收人,为告警所属项目的联系人:
项目组邮件:项目负责人邮件,或项目组产品线邮件,多个邮箱由英文逗号分割,不要留有空格;作为发送告警邮件、微信的依据
项目组号码:项目负责人手机号;多个号码由英文逗号分隔,不要留有空格;作为发送告警短信的依据
4. 配置告警策略,统计维度是每分钟。也就是这分钟出现了3个异常,它会在下一分钟推送出来。
日志内容剪切出来大概长这样。
。