此文章要有一定的基础,至少对javassist,javaagent 有所了解,
如果不是很熟悉的同学,请看下
Service + Control + jdbc 插桩埋点实现
一、项目架构介绍
二、采集端执行流程说明
需求与目标
采集指定数据,服务响应能、WEB响应性能、JDBC响应性能
处理流程
1、判定谁是采集目标类
2、构建插桩后的Class字节
3、采集方法运行时信息
4、上传运行时信息
结论:(绝对必须这么去做)
所有采集器必须要有判断是否监控目标的方法。
所有的采集器必须对我们的Class进行改造,生成插桩之后的字节码。
得出一个接口:Collects
记录开始信息、结束信息、异常信息、统计上传信息。(一般情况都会这么去做)
得出一个抽象类:AbstracetCollect
通用的方法:开始信息、结束信息、异常信息、统计上传信息
三、采集端架构UML类图及介绍
AgentMain:
监听器入口方法,所有采集器注册至该对象。由该对象的transform 来传递改造后的Class byte 至 ClassLoader进行加载。
Collect:
采集器接口,isTarget方法判定指类是否为采集目录,transform 构建插桩后的Class
AbstractCollects:
采集器的通用方法实现:begin 采集方法执行开始信息,error 采集异常信息 ,end 采集方法的结束信息。sendStatisticByHttp 基于Http 上传统计信息。
AgentLoader:
采集类修改器:updateMethod 改造指定方法已插入监听代码。toBytecote() 构建修改后的类字节。
四、Service 采集
问题:怎么判定目标类?基于XML 的配置如何 判定Service 为采集目标?
只能基于配置完成service 服务的判断。
判定目标方法?
公共的、非静态、非本地
监听方法代码构建
MethodSrcBuild: 开始代码、异常时执行代码、结束时执行代码。
统计信息传递
ServiceStatistics
异常堆栈传递
sendErrorStackByHttp("", throwable);
五、Control 采集
判定目标类?
基于@Control判定是否为采集目标。
判定目标方法?
屏蔽非公共方法、屏蔽静态方法、屏蔽本地方法、必须带上 RequestMapping 注解
获取URL地址
- 类 @RequestMapping 注解获取 value值.
- 方法 @RequestMapping 注解获取value值.
- 以上value值基于正则表达示获取.
六、JDBC 采集
判定目标类?
基于NonRegisteringDriver类下的
判断方法?
接下来展示一些相关代码类
=========================================分割线==============================================
AgentMain 类
package com.apm.init;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.apm.collects.JdbcCommonCollects;
import com.apm.collects.SpringControllerCollects;
import com.apm.collects.SpringServiceCollects;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.LoaderClassPath;
/**
* 可看
* 监听器入口方法,所有采集器注册至该对象。
* 由该对象的transform 来传递改造后的Class byte 至 ClassLoader进行加载。
*/
public class AgentMain implements ClassFileTransformer {
protected static AgentMain agentMain;
private static Collect[] collects; // 采集器集合
private Map<ClassLoader, ClassPool> classPoolMap = new ConcurrentHashMap<ClassLoader, ClassPool>();
// 上传地址
// 参数:
// pro.key=
// 访问远程服务 获取属性配置
/**
* mian执行后在去执行
* @param args
* @param inst
*/
public static void agentmain(String args, Instrumentation inst) {
inst.addTransformer(new DefineTransformer(), true);
}
static class DefineTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
System.out.println("AgentMainTest -- premain load Class:" + className);
return classfileBuffer;
}
}
private static final ArrayList<String> keys;
static {
String paramKesy[] = {"server", "key", "secret"};
keys = new ArrayList<String>();
keys.addAll(Arrays.asList(paramKesy));
}
// 在应用启动前调用
public static void premain(String agentArgs, Instrumentation inst) {
// if (agentArgs != null) {
// String[] paramGroup = agentArgs.split(",");
// for (String param : paramGroup) {
// String[] keyValue = param.split("=");
// if (keys.contains(keyValue[0])) {
// System.setProperty("$bit_" + keyValue[0], keyValue[1]);
// }
// }
// }
// // 验主验置
// if (System.getProperty("$bit_server") == null) {
// System.setProperty("$bit_server", "http://api.ibitedu.com/receive");
// }
// Assert.checkNull(System.getProperty("$bit_key"),"param key is not null");
// Assert.checkNull(System.getProperty("$bit_secret"),"param key is not null");
//主要監控那個目標
collects = new Collect[]{
SpringServiceCollects.INSTANCE,
JdbcCommonCollects.INSTANCE,
SpringControllerCollects.INSTANCE
};
agentMain = new AgentMain();
inst.addTransformer(agentMain);
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
// System.out.println("transform -- premain load Class:" + className);
if (className == null || loader == null
|| loader.getClass().getName().equals("sun.reflect.DelegatingClassLoader")
|| loader.getClass().getName().equals("org.apache.catalina.loader.StandardClassLoader")
|| loader.getClass().getName().equals("javax.management.remote.rmi.NoCallStackClassLoader")
|| loader.getClass().getName().equals("com.alibaba.fastjson.util.ASMClassLoader")
|| className.indexOf("$Proxy") != -1
|| className.startsWith("java")
) {
return null;
}
if(className.startsWith("com/eprintServer/controller") || className.startsWith("com/alibaba/druid/proxy/jdbc/ConnectionProxyImpl")) {
if (!classPoolMap.containsKey(loader)) {
ClassPool classPool = new ClassPool();
classPool.insertClassPath(new LoaderClassPath(loader));
classPoolMap.put(loader, classPool);
}
ClassPool cp = classPoolMap.get(loader);
try {
className = className.replaceAll("/", ".");
CtClass cclass = cp.get(className);
for (Collect c : collects) {
if (c.isTarget(className, loader, cclass)) { // 仅限定只能转换一次.
byte[] bytes = c.transform(loader, className, classfileBuffer, cclass);
//File f = new File("/Users/tommy/git/bit-monitoring-agent/target/" + cclass.getSimpleName() + ".class");
//Files.write(f.toPath(), bytes);
// System.out.println(String.format("%s bit APM agent insert success", className));
return bytes;
}
}
} catch (Throwable e) {
new Exception(String.format("%s APM agent insert fail", className), e).printStackTrace();
}
}
return new byte[0];
}
public static void main(String[] args) {
int i=10;System.out.println(i++);
String str1 = "通话";
String str2 = "重地";
String str3 = new String("通話");
System. out. println(String. format("str1:%d | str2:%d", str1.hashCode(),str2.hashCode()));
System. out. println(str1. equals(str2));
System.out.println(Math.round(-1.5));
}
}
Collect 接口类
package com.apm.init;
import javassist.CtClass;
/**
* 采集接口
*/
public interface Collect {
/**
* 判断是否为采集目录
*
* @param className
* @param loader
* @param ctclass
* @return
*/
public boolean isTarget(String className, ClassLoader loader,
CtClass ctclass);
/**
* 对目标类进行转
* @param loader
* @param className
* @param classfileBuffer
* @param ctclass
* @return
* @throws Exception
*/
public byte[] transform(ClassLoader loader, String className,
byte[] classfileBuffer, CtClass ctclass) throws Exception;
}
AbstractCollects 类
package com.apm.init;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.math.BigInteger;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.apm.collects.ErrorLog;
import com.apm.common.NetUtils;
import com.apm.json.JsonWriter;
/**
* 统计信息
*/
public abstract class AbstractCollects {
// 统一线程池
private final static ExecutorService threadService;
private static final String localIp;
private static long rejectedCount = 0;
static {
// 采样率配置
// 采样率 自动调节\手动调 节
// 随机的方式
/**
* 核心线程:20
* 最大线程:200
* 最大队例:1000
*/
threadService = new ThreadPoolExecutor(20, 200,
20000L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1000),
new RejectedExecutionHandler() {
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
rejectedCount++;
System.err.println("upload Task rejected from " +
executor.toString() + " rejectedCount:" + rejectedCount);
}
});
localIp = NetUtils.getLocalHost();
}
@NotProguard
public Statistics begin(String className, String method) {
Statistics s = new Statistics();
s.begin = System.currentTimeMillis();
s.createTime = System.currentTimeMillis();
return s;
}
@NotProguard
public void end(Statistics stat) {
stat.end = System.currentTimeMillis();
stat.userTime = stat.end - stat.begin;
sendStatistics(stat);
// System.out.println("代理结束:" + stat.toString());
}
@NotProguard
public void error(Statistics stat, Throwable throwable) {
if (stat != null) {
stat.errorMsg = throwable.getMessage();
stat.errorType = throwable.getClass().getName();
if (throwable instanceof InvocationTargetException) {
stat.errorType = ((InvocationTargetException) throwable).getTargetException().getClass().getName();
stat.errorMsg = ((InvocationTargetException) throwable).getTargetException().getMessage();
}
}
if (throwable != null) {
sendErrorStackByHttp("", throwable);
}
}
/**
* 发送统计信息
*
* @param stat
*/
public abstract void sendStatistics(final Statistics stat);
protected void sendErrorStackByHttp(String errorMsg, Throwable throwable) {
ErrorLog errorLog = new ErrorLog();
if (throwable instanceof InvocationTargetException) {
errorLog.setErrorType(((InvocationTargetException) throwable).getTargetException().getClass().getName());
errorLog.setErrorMsg(((InvocationTargetException) throwable).getTargetException().getMessage());
} else {
errorLog.setErrorType(throwable.getClass().getName());
errorLog.setErrorMsg(throwable.getMessage());
}
errorLog.setKeyId(System.getProperty("$bit_key"));
errorLog.setIp(localIp);
errorLog.setLogType("error");
errorLog.setCreateTime(System.currentTimeMillis());
// 计算异常堆栈
{
ByteArrayOutputStream out = new ByteArrayOutputStream();
PrintStream s = new PrintStream(out);
throwable.printStackTrace(s);
errorLog.setStatck(out.toString());
}
execHttp("errorLog", errorLog);
}
protected void execHttp(final String type, final Object data) {
System.err.println("---------"+toJson(data));
Runnable runn = new Runnable() {
public void run() {
try {
String remoteUrl = System.getProperty("$bit_server");
if(remoteUrl!=null) {
// remoteUrl += "?";
String key = System.getProperty("$bit_key");
String secret = System.getProperty("$bit_secret");
long currentTime = System.currentTimeMillis();
// 计算签名
String sign = secret + key + type + currentTime + secret;
sign = getMD5(sign.toUpperCase());
String params = "";
params += "type=" + type;
params += "&sign=" + sign;
params += "&key=" + key;
params += "&time=" + currentTime;
params += "&data=" + URLEncoder.encode(toJson(data),"UTF-8");
URL url = new URL(remoteUrl);
PrintWriter out = null;
BufferedReader in = null;
String result = "";
try {
URL realUrl = new URL(remoteUrl);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
// conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setUseCaches(false);
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
// 获取URLConnection对象对应的输出流
out = new PrintWriter(new OutputStreamWriter(conn.getOutputStream(),"UTF-8"));
// 发送请求参数
out.print(params);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(
new InputStreamReader(conn.getInputStream(), "UTF-8"));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
if(!"ok".equals(result)){
System.err.println("bit apm upload fail :"+result);
}
} catch (Exception e) {
throw new RuntimeException("上传失败", e);
// logger.error("发送 POST 请求出现异常!",e);
}
//使用finally块来关闭输出流、输入流
finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
};
threadService.execute(runn);
}
protected void sendStatisticByHttp(final Statistics stat, final String type) {
// 通过后台线程发送至监控中心
stat.keyId = System.getProperty("$bit_key");
execHttp(type, stat);
}
public static String getAnnotationValue(String key, String annotationDesc) {
String regex = String.format("value=\\{\".*\"\\}");
Pattern r = Pattern.compile(regex);
Matcher matcher = r.matcher(annotationDesc);
if (matcher.find()) {
return matcher.group().substring(key.length() + 3, matcher.group().length() - 2);
}
return null;
}
// 统计信息
@NotProguard
public static class Statistics {
public Long begin;
public Long end;
public Long userTime;
public String errorMsg;
public String errorType;
public Long createTime;
public String keyId;
public String ip = localIp;
public String logType;
public Statistics() {
}
public Statistics(Statistics copy) {
this.begin = copy.begin;
this.createTime = copy.createTime;
this.end = copy.end;
this.errorMsg = copy.errorMsg;
this.errorType = copy.errorType;
this.keyId = copy.keyId;
this.ip = copy.ip;
this.logType = copy.logType;
this.userTime = userTime;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("{");
if (begin != null)
sb.append("\"begin\":").append(begin);
if (end != null)
sb.append(", \"end\":").append(end);
if (errorMsg != null)
sb.append(", \"errorMsg\":\"").append(errorMsg).append('\"');
if (errorType != null)
sb.append(", \"errorType\":\"").append(errorType).append('\"');
if (createTime != null)
sb.append(", \"createTime\":").append(createTime);
if (keyId != null)
sb.append(", \"key\":\"").append(keyId).append('\"');
if (sb.substring(1, 1).equals(",")) {
sb.delete(1, 2);
}
sb.append('}');
return sb.toString();
}
public String toJsonString() {
final StringBuilder sb = new StringBuilder("{");
if (begin != null)
sb.append("\"begin\":").append(begin);
if (end != null)
sb.append(", \"end\":").append(end);
if (errorMsg != null)
sb.append(", \"errorMsg\":\"").append(errorMsg).append('\"');
if (errorType != null)
sb.append(", \"errorType\":\"").append(errorType).append('\"');
if (createTime != null)
sb.append(", \"createTime\":").append(createTime);
if (sb.substring(1, 2).equals(",")) {
sb.delete(1, 2);
}
sb.append('}');
return sb.toString();
}
}
private static String toJson(Object obj) {
Map<String, Object> item = new HashMap<String, Object>();
item.put("TYPE", false);
item.put(JsonWriter.SKIP_NULL_FIELDS, true);
String json = JsonWriter.objectToJson(obj, item);
return json;
}
public static String getMD5(String content) {
try {
// 生成一个MD5加密计算摘要
MessageDigest md = MessageDigest.getInstance("MD5");
// 计算md5函数
md.update(content.getBytes());
return new BigInteger(1, md.digest()).toString(16);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
SpringControllerCollects 采集类其他类看github
package com.apm.collects;
import com.apm.init.AbstractCollects;
import com.apm.init.AgentLoader;
import com.apm.init.Collect;
import com.apm.init.NotProguard;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;
/**
* 对 Controller 层进行采集
*
*/
@NotProguard
public class SpringControllerCollects extends AbstractCollects implements Collect {
@NotProguard
public static SpringControllerCollects INSTANCE = new SpringControllerCollects();
private static final String beginSrc;
private static final String endSrc;
private static final String errorSrc;
private String rootRequestUrl = "";
static {
StringBuilder sbuilder = new StringBuilder();
sbuilder.append("com.apm.collects.SpringControllerCollects instance= ");
sbuilder.append("com.apm.collects.SpringControllerCollects.INSTANCE;\r\n");
sbuilder.append("com.apm.collects.SpringControllerCollects.WebStatistics statistic =(com.apm.collects.SpringControllerCollects.WebStatistics)instance.begin(\"%s\",\"%s\");");
sbuilder.append("statistic.urlAddress=\"%s\";");
beginSrc = sbuilder.toString();
// sbuilder = new StringBuilder();
sbuilder.setLength(0);
sbuilder.append("instance.end(statistic);");
endSrc = sbuilder.toString();
sbuilder.setLength(0);
// sbuilder = new StringBuilder();
sbuilder.append("instance.error(statistic,e);"); //父类的方法
errorSrc = sbuilder.toString();
}
/**
* 判断是否是采集对象
*/
public boolean isTarget(String className, ClassLoader loader, CtClass ctclass) {
boolean result = false;
try {
for (Object obj : ctclass.getAnnotations()) {
// 通过正则表达示计算出RequestMapping 地址
if (obj.toString().startsWith("@org.springframework.web.bind.annotation.RequestMapping")) {
rootRequestUrl = getAnnotationValue("value", obj.toString());
} else if (obj.toString().startsWith("@org.springframework.stereotype.Controller")) {
result = true;
}
}
} catch (ClassNotFoundException e) {
System.err.println(String.format("bit apm run error targetClassName=%s errorMessage=%s",className,e.getClass().getSimpleName()+":"+e.getMessage()));
}
return result;
}
@NotProguard
@Override
public Statistics begin(String className, String method) {
WebStatistics webStat = new WebStatistics(super.begin(className, method));
webStat.controlName = className;
webStat.methodName = method;
webStat.logType="web";
return webStat;
}
@Override
public void sendStatistics(Statistics stat) {
sendStatisticByHttp(stat,"webLog");
}
/**
* 对其转换 (插入监控代码)
*/
public byte[] transform(ClassLoader loader, String className, byte[] classfileBuffer, CtClass ctclass) throws Exception {
AgentLoader byteLoade = new AgentLoader(className, loader, ctclass);
CtMethod[] methods = ctclass.getDeclaredMethods();
for (CtMethod m : methods) {
String requestUrl;
// 屏蔽非公共方法
if (!Modifier.isPublic(m.getModifiers())) {
continue;
}
// 屏蔽静态方法
if (Modifier.isStatic(m.getModifiers())) {
continue;
}
// 屏蔽本地方法
if (Modifier.isNative(m.getModifiers())) {
continue;
}
// 必须带上 RequestMapping 注解
if ((requestUrl = getRequestMappingValue(m)) == null) {
continue;
}
AgentLoader.MethodSrcBuild build = new AgentLoader.MethodSrcBuild();
build.setBeginSrc(String.format(beginSrc, className, m.getName(), rootRequestUrl + requestUrl));
build.setEndSrc(endSrc);
build.setErrorSrc(errorSrc);
byteLoade.updateMethod(m, build);
}
return byteLoade.toBytecote();
}
private String getRequestMappingValue(CtMethod m) throws ClassNotFoundException {
for (Object s : m.getAnnotations()) {
if (s.toString().startsWith("@org.springframework.web.bind.annotation.RequestMapping")) {
String val = getAnnotationValue("value", s.toString());
return val==null?"/":val;
}
}
return null;
}
@NotProguard
public static class WebStatistics extends Statistics {
public String urlAddress; //url 地址
public String controlName; //服务名称
public String methodName;// 方法名称
public WebStatistics(Statistics s) {
super(s);
}
}
}
采集类修改器 AgentLoader
package com.apm.init;
import java.io.IOException;
import javassist.CannotCompileException;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;
/**
* agent 装载器 主要作用
* 1:构建代理监听环境
* 2:为目标类载入代理监听
*/
public class AgentLoader {
@SuppressWarnings("unused")
private final String className;
@SuppressWarnings("unused")
private final ClassLoader loader;
private final CtClass ctclass;
public AgentLoader(String className,
ClassLoader loader,
CtClass ctclass) {
this.className = className;
this.loader = loader;
this.ctclass = ctclass;
}
/*
* 插入 监听 method
*/
public void updateMethod(CtMethod method, MethodSrcBuild srcBuild) throws CannotCompileException, NotFoundException {
CtMethod ctmethod = method;
String methodName = method.getName();
// 重构被代理的方法名称
// 基于原方法复制生成代理方法
CtMethod agentMethod = CtNewMethod.copy(ctmethod, methodName, ctclass, null);
agentMethod.setName(methodName + "$agent");
ctclass.addMethod(agentMethod);
// 原方法重置为代理执行
ctmethod.setBody(srcBuild.buildSrc(ctmethod));
}
/**
* 生成新的class 字节码 ,
*
* @param className
* @param loader
* @return
* @throws NotFoundException
* @throws Exception
*/
public byte[] toBytecote() throws IOException, CannotCompileException {
return ctclass.toBytecode();
}
/**
* 內部類 插莊
*
*/
public static class MethodSrcBuild {
private String beginSrc;
private String endSrc;
private String errorSrc;
public MethodSrcBuild setBeginSrc(String beginSrc) {
this.beginSrc = beginSrc;
return this;
}
public MethodSrcBuild setEndSrc(String endSrc) {
this.endSrc = endSrc;
return this;
}
public MethodSrcBuild setErrorSrc(String errorSrc) {
this.errorSrc = errorSrc;
return this;
}
public String buildSrc(CtMethod method) {
String result;
try {
String template = method.getReturnType().getName().equals("void") ? voidSource : source;
String bsrc = beginSrc == null ? "" : beginSrc;
String eSrc = errorSrc == null ? "" : errorSrc;
String enSrc = endSrc == null ? "" : endSrc;
String src = String.format(template, bsrc, method.getName(), eSrc, enSrc);
return src;
} catch (NotFoundException e) {
throw new RuntimeException(e);
}
}
//参数写法
//$$ 表示 arg1 arg2 arg3 ...
//$1 表示 arg1
//$2 表示 arg2
//$args 表示 Object[]
//$w 表示自动返回原来的类型
final static String source = "{\n"
+ "%s"
+ " Object result=null;\n"
+ " try {\n"
+ " result=($w)%s$agent($$);\n"
+ " } catch (Throwable e) {\n"
+ "%s"
+ " throw e;\n"
+ " }finally{\n"
+ "%s"
+ " }\n"
+ " return ($r) result;\n"
+ "}\n";
final static String voidSource = "{\n"
+ "%s"
+ " try {\n"
+ " %s$agent($$);\n"
+ " } catch (Throwable e) {\n"
+ "%s"
+ " throw e;\n"
+ " }finally{\n"
+ "%s"
+ " }\n"
+ "}\n";
}
}
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.apm</groupId>
<artifactId>m-javassist</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>m-javassist</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.24.1-GA</version>
</dependency>
<!--<dependency>
<groupId>javax.xml.ws</groupId>
<artifactId>jaxws-api</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.30</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
<dependency>
<groupId>oro</groupId>
<artifactId>oro</artifactId>
<version>2.0.8</version>
</dependency> -->
</dependencies>
<build>
<finalName>m-javassist</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<archive>
<!--自动添加META-INF/MANIFEST.MF -->
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<Premain-Class>com.apm.init.AgentMain</Premain-Class>
<Agent-Class>com.apm.init.AgentMain</Agent-Class>
<Can-Redefine-Classes>false</Can-Redefine-Classes>
<Can-Retransform-Classes>false</Can-Retransform-Classes>
</manifestEntries>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<!-- 配置执行器 -->
<execution>
<!-- this is used for inheritance merges -->
<id>make-assembly</id>
<!-- 指定在打包节点执行jar包合并操作绑定到package生命周期阶段上 -->
<phase>package</phase>
<goals>
<!-- 单例 只运行一次 -->
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
最终打包成jar
在 VM 上加上参数 -javaagent:F:\myworkspace\m-javassist\target\m-javassist-jar-with-dependencies.jar=canshu
指定打包后的目录, canshu ==参数
然后启动后看日志,会有输出json格式的日志
比如
{"urlAddress":"/index/main","controlName":"com.eprintServer.controller.IndexController","methodName":"mian","begin":1602056793201,"end":1602056793201,"userTime":0,"createTime":1602056793201,"ip":"192.168.41.58","logType":"web"}
到这里就差将数据上传到统计服务器做处理了