Byte buddy not able to pass modify args to original method
private final Level leastSpecificLevelToEncrypt;
public LogInterceptor(Level leastSpecificLevelToEncrypt) {
this.leastSpecificLevelToEncrypt = leastSpecificLevelToEncrypt;
}
@RuntimeType
public Object intercept(@AllArguments Object[] args, @Origin Method method, @SuperCall Callable<?> superMethod) throws Exception {
System.out.println("Intercepting log method");
// Log the object, method, and parameters
System.out.println("Invoking method: " + method.getName());
System.out.println("On object: " + args[0]);
System.out.println("With parameters:");
for (int i = 0; i < args.length; i++) {
System.out.println(" args[" + i + "]: " + (args[i] != null ? args[i].getClass().getName() : "null") + " = " + args[i]);
}
Level level = (Level) args[0];
Message message = (Message) args[4];
Throwable throwable = (Throwable) args[5];
// Apply encryption logic if necessary
if (LogMessageEncryptor.getInstance().isInitialized() && level.isLessSpecificThan(leastSpecificLevelToEncrypt)) {
System.out.println("Encrypting log message: " + message.getFormattedMessage());
EncodedMessage encodedMessage = new EncodedMessage(Encode.forJava(message.getFormattedMessage()));
args[4] = LogMessageEncryptor.INSTANCE.encrypt(encodedMessage, throwable); // Replace the message with the encrypted message
//args[4] = new CustomMessageWrapper(LogMessageEncryptor.INSTANCE.encrypt(encodedMessage, throwable));
args[5] = null; // Clear the throwable
System.out.println("args[4] after replacement: " + args[4].getClass().getName());
System.out.println("args[4] content: " + ((Message) args[4]).getFormattedMessage());
}
// Log the invocation of the original method
System.out.println("LogInterceptor: Invoking original method with modified arguments");
//
// System.out.println("Encrypted message: " + args[4].toString());
for (int i = 0; i < args.length; i++) {
System.out.println(" args[" + i + "]: " + (args[i] != null ? args[i].getClass().getName() : "null") + " = " + args[i]);
}
// Invoke the original method with the modified arguments
// return method.invoke(args[0], args);
System.out.println("Before method.invoke: args[4] = " + args[4].getClass().getName());
Object result = superMethod.call(); //method.invoke(args[0], args);
System.out.println("After method.invoke: args[4] = " + args[4].getClass().getName());
return result;
}
}
Final logs printed on the client console aren't encrypted. However, if I use the cglib base implementation instead of bytebuddy with the same logic, the logs are encrypted. Below are the logs with bytebuddy:
Intercepting log method Invoking method: log On object: STATUS With parameters: args[0]: org.apache.logging.log4j.Level = STATUS args[1]: null = null args[2]: java.lang.String = org.apache.logging.log4j.spi.AbstractLogger args[3]: null = null args[4]: org.apache.logging.log4j.message.ParameterizedMessage = ParameterizedMessage[messagePattern=NLE started on port {}, stringArgs=[8088], throwable=null] args[5]: null = null Encrypting log message: NLE started on port 8088 encrypt called for message: NLE started on port 8088 args[4] after replacement: x.x.x.toolkit.log4j2.EncryptedMessage EncryptedMessage.getFormattedMessage() called args[4] content: encrypted log >>EKquNaISFbSXLN6vu3a5lMSEn84TGLxJe1gsJn0oi5w=<< Key >>Q7nPqeWHCg6xPBa3hGl6FPnp7p7wxqeWG2myhL3g5heVG9vapITvwcJgKyqggcy7t8EaiJmEtUR4r71cD4Z/YwQ7snNk0ri5xkHaUDMW7nCNDJ80QvA7CEWG6Vimtz7NmvyepB1wMX0DKmVYccj6Z1qMQOXea/dP+f2Gdnuly6MCU0Esy2QGLskEFH/MoO/a93gG8zCSEk9Yr9w7iN8j/9AtqZ/gAGbnUgWWmtbeeBvHiGe5lkERFX1MWIlxG12zZEm7wOLQv2Xajj6IA8/ZOPXrcBZXJsvLDT1aDZYmyNFSBkBE4hhnI05caGN0lHVp/rEEMUkZqNWF+H6UsmFsHQ==<< LogInterceptor: Invoking original method with modified arguments args[0]: org.apache.logging.log4j.Level = STATUS args[1]: null = null args[2]: java.lang.String = org.apache.logging.log4j.spi.AbstractLogger args[3]: null = null args[4]: x.x.x.toolkit.log4j2.EncryptedMessage = x.x.x.log4j2.EncryptedMessage@1c55237e args[5]: null = null Before method.invoke: args[4] = x.x.x.toolkit.log4j2.EncryptedMessage After method.invoke: args[4] = x.x.x.toolkit.log4j2.EncryptedMessage 2025-05-13 09:29:10,375 STATUS [ main] c.n.n.mStatusLogger : NLE started on port 8088 logs from cglib based implementation:
EncryptedMessage.getFormattedMessage() called log: Object: c.n.n.mStatusLogger:STATUS in AsyncContext@179d3b25 log: Args: [STATUS, null, org.apache.logging.log4j.spi.AbstractLogger, null, x.x.x.toolkit.log4j2.EncryptedMessage@3fde2209, null] EncryptedMessage.getFormattedMessage() called EncryptedMessage.getFormattedMessage() called 2025-05-13 11:31:00,354 STATUS [ main] c.n.n.ApplicationReadyStatusLogger : encrypted log >>QBwj5PL1zdNFHW45smJVhGnU1YmR4SWRGHykyCk+o58=<< Key >>kxv8NnIdX7HP5rg9OQcXHpm0p0eAMEeyVR23zEt3jeLDKYagQ7f/xggACKIopWHKm+Jq3vN3XcMuvL+49Qlap5FNzmU2HLOVqjlhhcLfOrVYmITNH/7gyvZyyih4mERDOxOpqyq8u1yl6fShBnNtbB00hdhFvFHqKSpPMpqhBEjmf/p+JP46yNOhHBvVqbeT3Wh6RKYqy4FH6miTJnDm+8BzujNdvFmh4gk3MXHvWzJW78+nkcMf6fkAP/PEf/WhE46FjASZfRSOmq+7uWz4ZBHDOE18y4Y1VXHPUZsCoCSo3dWZFankWREGla1POgzFBCkjTskhYm18wWnw23kWhw==<< EncryptedMessage.getFormattedMessage() called Log encryption enabled: true
The issue seems to occur when the ByteBuddy-based implementation passes x.x.x.toolkit.log4j2.EncryptedMessage as an argument to the original method, which is the log method. In this case, the EncryptedMessage.getFormattedMessage() method isn't called by log4j2. However, when the same encrypted message is passed by the CGLIB-based implementation, the EncryptedMessage.getFormattedMessage() method is called, and the final logs printed on the client console are encrypted.
Here is how I'm creating EnhancedLogger:
Class<? extends Logger> enhancedLoggerClass = new ByteBuddy()
.subclass(loggerClass)
.method(ElementMatchers.named("logMessage")
.and(ElementMatchers.takesArguments(String.class, Level.class, Marker.class, Message.class, Throwable.class)))
.intercept(MethodDelegation.to(new LogMessageInterceptor(leastSpecificLevelToEncrypt)))
.method(ElementMatchers.named("log")
.and(ElementMatchers.takesArguments(Level.class, Marker.class, String.class, StackTraceElement.class, Message.class, Throwable.class)))
.intercept(MethodDelegation.to(new LogInterceptor(leastSpecificLevelToEncrypt)))
.make()
.load(loggerClass.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded();
I think bytebuddy isn't able to pass modified args to original method. Isn't bytebuddy designed to support this
Method delegation is designed to avoid this. Try out Advice where you inline code into the instrumented method and operate on the actual arguments.
@raphw Do you mean to say if I use Advice instead of delegation , I would be able to pass modified args to original method? If possible, sample could would be of great help. Thanks!
Yes, you would need to use Advice for this.