利用javassist,javaagent 打造个简单的性能监控 APM(学习)

    科技2024-06-20  75

     

    此文章要有一定的基础,至少对javassist,javaagent 有所了解,

    如果不是很熟悉的同学,请看下

    https://www.cnblogs.com/rickiyang/p/11368932.html

     

    项目地址:https://github.com/MJFuture/m-javassist.git

     

    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; /** * https://www.cnblogs.com/rickiyang/p/11368932.html 可看 * 监听器入口方法,所有采集器注册至该对象。 * 由该对象的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"}

     到这里就差将数据上传到统计服务器做处理了

    Processed: 0.017, SQL: 8