如何利用Java Agent 做Spring MVC Controller 层的出参入参打印日志
作者:笔墨登场说说
本文介绍了如何使用JavaAgent进行Spring MVC Controller层的出参入参打印日志,首先,建立了一个包含javassist和fastJSON依赖的Agent jar工程,并创建了一个Agent类,然后,编译并部署了这个Agent jar,最后,在Demo Web工程中启用Agent以实现日志打印
许多开发使用的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 层出参入参打印日志内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!