如何利用Java Agent 做Spring MVC Controller 层的出参入参打印日志
许多开发使用的spring aop来做切面 今天尝试一下使用JVM层面的切面
1、建立 agent jar工程
创建工程 logging-agent 使用POM为 javassist 日志
如下:使用了字节码 javassist
如果想处理springaop 代理的请增加依赖否则就不需要
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.3.21</version> <!-- 确保使用最新版本 --> </dependency> <!-- AspectJ Weaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.7</version> <!-- 确保使用最新版本 --> </dependency>
为了方便使用fastJSON 做序列化
完整POM如下
<?xml version="1.0" encoding="UTF-8"?> <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>org.example</groupId> <artifactId>logging-agent</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.28.0-GA</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.3.21</version> <!-- 确保使用最新版本 --> </dependency> <!-- AspectJ Weaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.7</version> <!-- 确保使用最新版本 --> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.83</version> <!-- 确保使用最新版本 --> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> <executions> <execution> <id>attach-sources</id> <goals> <goal>jar</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.4</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>org.logging.LoggingAgent</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.1.0</version> <configuration> <archive> <!--自动添加META-INF/MANIFEST.MF --> <manifest> <addClasspath>true</addClasspath> </manifest> <manifestEntries> <Premain-Class>org.logging.LoggingAgent</Premain-Class> <Agent-Class>org.logging.LoggingAgent</Agent-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> </plugin> </plugins> </build> </project>
2、建立一个Agent类
package org.logging; import com.alibaba.fastjson.JSON; import javassist.*; import org.springframework.aop.framework.AopProxyUtils; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.security.ProtectionDomain; import java.util.*; public class LoggingAgent { public static void premain(String agentArgs, Instrumentation inst) { inst.addTransformer(new LoggingTransformer()); } static Set<Class<?>> skipLogClassSet = new HashSet<>(); static Map<Class<?>, Boolean> decisionSkipClassMap = new HashMap<>(); static { skipLogClassSet.add(forName("org.apache.catalina.connector.RequestFacade")); skipLogClassSet.add(forName("javax.servlet.ServletRequest")); skipLogClassSet.add(forName("javax.servlet.ServletResponse")); } public static Class<?> forName(String clazz) { try { return Class.forName(clazz); } catch (ClassNotFoundException e) { e.printStackTrace(); } return Void.class; } public static String toJSONString(Object[] a) { if (a == null) return "null"; int iMax = a.length - 1; if (iMax == -1) return "[]"; StringBuilder b = new StringBuilder(); b.append('['); for (int i = 0; ; i++) { // b.append(String.valueOf(a[i])); Class<?> clazz = a[i].getClass(); System.out.println(clazz); if (!decisionSkipClassMap.containsKey(clazz)) { // 检查类是否实现了每个接口 for (Class<?> interfaceClass : skipLogClassSet) { if (interfaceClass.isAssignableFrom(clazz)) { System.out.println("myObject 的类实现了 " + interfaceClass.getName() + " 接口"); decisionSkipClassMap.put(clazz, true); } else { System.out.println("myObject 的类没有实现 " + interfaceClass.getName() + " 接口"); if (decisionSkipClassMap.containsKey(clazz) && decisionSkipClassMap.get(clazz)) { //nothing System.out.println("myObject 的类没有实现 " + interfaceClass.getName() + " 接口 但是实现了其他忽略接口"); } else { decisionSkipClassMap.put(clazz, interfaceClass.isAssignableFrom(clazz)); } } } } if (!decisionSkipClassMap.get(clazz)) { b.append(JSON.toJSONString(a[i])); } if (i == iMax) return b.append(']').toString(); b.append(", "); } } public static class LoggingTransformer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { // 忽略不需要处理的类 if (!className.startsWith("com/xdx/interfaces/facade")) { return null; } // Skip Spring CGLIB proxy classes if (className.contains("$$EnhancerBySpringCGLIB$$")) { return null; } // Skip Spring CGLIB FastClass proxy classes if (className.contains("$$FastClassBySpringCGLIB$$")) { return null; } try { String finalClassName = getFinalClassName(className.replace("/", ".")); ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(this.getClass())); CtClass ctClass = pool.get(finalClassName); // 获取类的所有声明字段 CtField[] fields = ctClass.getDeclaredFields(); String logger = null; // 打印字段的名称和类型 for (CtField field : fields) { System.out.println("Field Name: " + field.getName() + ", Field Type: " + field.getType().getName()); if ("org.slf4j.Logger".equals(field.getType().getName())) { logger = field.getName(); } } for (CtMethod method : ctClass.getDeclaredMethods()) { try { addLogging(method, logger); } catch (Exception e) { System.err.println("Failed to instrument method: " + method.getName()); e.printStackTrace(); } } return ctClass.toBytecode(); } catch (NotFoundException e) { // Log exception and continue without instrumentation System.err.println("Class not found: " + className + " - " + e.getMessage()); } catch (Exception e) { e.printStackTrace(); } return classfileBuffer; } private String getFinalClassName(String className) { if (className.contains("$$EnhancerBySpringCGLIB$$") || className.contains("$$FastClassBySpringCGLIB$$")) { try { Class<?> proxyClass = Class.forName(className); Class<?> targetClass = AopProxyUtils.ultimateTargetClass(proxyClass); return targetClass.getName(); } catch (ClassNotFoundException e) { // Handle exception and return the original className e.printStackTrace(); } } return className; } private void addLogging(CtMethod method, String logger) throws CannotCompileException { if (Objects.isNull(logger)) { addLogging2(method); } else { addLogging1(method, logger); } } private void addLogging1(CtMethod method, String logger) throws CannotCompileException { // method.insertBefore("System.out.println(\"[ENTER] Method: " + method.getName() + " Arguments: \" + java.util.Arrays.toString($args));"); method.insertBefore(logger + ".info(\"[ENTER1] Method: " + method.getName() + " Arguments: \" +org.logging.LoggingAgent.toJSONString($args));"); // method.insertBefore("log.info(org.logging.LoggingAgent.toJSONString($args));"); method.insertAfter(logger + ".info(\"[EXIT1] Method: " + method.getName() + " Return: \" + $_);"); // method.insertBefore(logger + ".info(\"[EXIT] Method: " + method.getName() + " Return: \" + org.logging.LoggingAgent.toJSONString($_));"); } private void addLogging2(CtMethod method) throws CannotCompileException { method.insertBefore("org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(this.getClass());" + "logger.info(\"[ENTER2] Method: " + method.getName() + " Arguments: \" + org.logging.LoggingAgent.toJSONString($args));"); // method.insertBefore("org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(this.getClass());" + // "logger.info(\"[ENTER] Method: " + method.getName() + " Arguments: \" + java.util.Arrays.toString($args));"); method.insertAfter( "logger.info(\"[EXIT2] Method: " + method.getName() + " Return: \" + $_);", true);//src:表示插入的代码,这是一段 Java 源代码的 String。 // asFinally:这是一个 boolean 值,决定了插入的代码块是在正常返回后执行,还是在方法的正常返回和异常返回后都执行。 // method.insertAfter("org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(this.getClass());" + // "logger.info(\"[EXIT] Method: " + method.getName() + " Return: \" + logger.info(org.logging.LoggingAgent.toJSONString($_)));", true); } private void addLogging3(CtMethod method) throws CannotCompileException { method.insertBefore("System.out.println(\"[ENTER] Method: " + method.getName() + " Arguments: \" + java.util.Arrays.toString($args));"); method.insertAfter("System.out.println(\"[EXIT] Method: " + method.getName() + " Return: \" + $_);"); } } }
3、打agent jar
4、创建一个demoWeb工程
-javaagent:D:\code\logging-agent\target\logging-agent-1.0-SNAPSHOT.jar
为了能调试需要增加jar
到此这篇关于利用Java Agent 做Spring MVC Controller 层的出参入参打印日志的文章就介绍到这了,更多相关Spring MVC Controller 层出参入参打印日志内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
struts1之ActionServlet详解_动力节点Java学院整理
这篇文章主要介绍了struts1之ActionServlet详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧2017-09-09spring boot openfeign从此和httpClient说再见详析
这篇文章主要给大家介绍了关于spring boot openfeign从此和httpClient说再见的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起看看吧2018-06-06
最新评论