利用Java Agent 做Spring MVC Controller 层的出参入参打印日志

devtools/2024/11/13 23:51:34/

许多开发使用的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><transformerimplementation="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类

java">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)) {//nothingSystem.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 {@Overridepublic 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 classesif (className.contains("$$EnhancerBySpringCGLIB$$")) {return null;}// Skip Spring CGLIB FastClass proxy classesif (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 instrumentationSystem.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 classNamee.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 


http://www.ppmy.cn/devtools/133767.html

相关文章

【操作系统】输入/输出(I/O)管理

王道笔记 一、I/O管理描述 1.1 I/O设备的概念和分类 1.1.1 什么是I/O设备 “I/O”就是“输入/输出”&#xff08;Input/Output&#xff09; I/O设备机会可以将数据输入到计算机&#xff0c;或者可以接收计算机输出数据的外部设备&#xff0c;属于计算机中的硬件部件。下图就…

医疗影像分割 | 使用yolo v11训练自己的数据集

代码下载:GitHub - ultralytics/ultralytics: Ultralytics YOLO11 🚀 环境配置可以参考:目标检测:YOLOv11(Ultralytics)环境配置,适合0基础纯小白,超详细_yolov11环境配置-CSDN博客 自己的数据集标注教学(使用labelme):

【嵌入式开发】单片机CAN配置详解

0 前言 CAN外设作为一种传输速率较高&#xff0c;且连线较为简洁的通信协议&#xff0c;如今很多单片机内部都集成了CAN控制模块&#xff0c;这样只需要再外接一个CAN收发芯片&#xff0c;将TTL/CMOS电平转换成CAN协议的差分电平&#xff0c;就是一个完整的CAN收发节点。   最…

CLion配置QT开发环境

一、将qmake工程转为cmake工程&#xff08;方法一&#xff1a;用工具转换并做适当修改&#xff09; 1、工具链接&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1grW2QY3sW8X2JaHWM_ePPw 提取码&#xff1a;7at4 工具源码:https://github.com/milahu/qmake2cmake 2、执行…

发布 VectorTraits v3.0(支持 X86架构的Avx512系列指令集,支持 Wasm架构及PackedSimd指令集等)

文章目录 支持 X86架构的Avx512系列指令集支持Avx512时的输出信息 支持 Wasm架构及PackedSimd指令集支持PackedSimd时的输出信息VectorTraits.Benchmarks.Wasm 使用说明 新增了向量方法支持 .NET 8.0 新增的向量方法提供交织与解交织的向量方法YGroup3Unzip的范例代码 提供重新…

python实战(八)——情感识别(多分类)

一、任务目标 本文使用的是来自Kaggle的一个情感识别数据集&#xff0c;这个数据集的总数据量是5934条&#xff0c;标签为anger、fear、joy三种情感的其中一种&#xff0c;很明显是一个多分类任务。这里&#xff0c;我们将使用微调技巧进行深度学习建模&#xff0c;同时我们会比…

Makefile与CMake回顾

一、Makefile 1.gcc编译 预处理--->编译---->汇编---->链接 预处理&#xff1a;展开头文件&#xff0c;替换宏&#xff0c;删除注释 gcc -E ***.c -o ***.i 编译&#xff1a;检查语法的正确性&#xff0c;生成汇编文件 gcc -S ***.i -o ***.s 汇编&#xff1a;把汇编…

【Python进阶】Python网络协议与套接字编程:构建客户端和服务器

1、网络通信基础与网络协议 1.1 网络通信模型概述 网络通信是信息时代基石&#xff0c;它如同现实世界中的邮递系统&#xff0c;将数据从一处传递到另一处。其中&#xff0c;OSI七层模型与TCP/IP四层或五层模型是理解和构建网络通信的基础。 1.1.1 OSI七层模型与TCP/IP四层/…