Skip to content

Commit

Permalink
Initial commit of log method weaving hook
Browse files Browse the repository at this point in the history
  • Loading branch information
royteeuwen committed Jul 31, 2024
0 parents commit f0026f0
Show file tree
Hide file tree
Showing 11 changed files with 497 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.classpath
.project
.settings/
target/
.DS_Store
.idea/
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Sling Observability Weaving Hooks

Library of weaving hooks to ease the process of observability in out-of-the-box code

## Log Method Weaving Hook

When all else fails, and you have no logs available because there are no log statements in the out-of-the-box classes, use this Log Method Weaving Hook to add a dynamic log statements

### Usage

Install the bundle in start level 1 and add an OSGi config for every method log you would like to add.

Example, search for your classname, method name and amount of parameters you want to log and add an OSGi config `be.orbinson.sling.observability.weavinghooks.logmethod.LogMethodWeavingHookConfiguration~MyClass-doGet.cfg.json`

```json
{
"className": "my.package.MyClass",
"methodName": "doGet",
"amountOfParameters": 2
}
```

To make the weaving hook work, either a framework restart of an entire java process restart is required.

## Future

- Add weaving hooks to create custom spans and metrics using OpenTelemetry
138 changes: 138 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<?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/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>be.orbinson.sling</groupId>
<artifactId>sling-observability-weavinghooks</artifactId>
<packaging>bundle</packaging>
<version>0.0.1-SNAPSHOT</version>

<name>Sling Observability Weaving Hooks</name>

<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>5.1.9</version>
<extensions>true</extensions>
<configuration>
<outputDirectory>${basedir}/target/classes</outputDirectory>
<instructions>
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
<Bundle-Version>${project.version}</Bundle-Version>
<Embed-Dependency>asm,asm-util,asm-tree,asm-analysis</Embed-Dependency>
</instructions>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>

<properties>
<sling.host>localhost</sling.host>
<sling.port>8080</sling.port>
<sling.user>admin</sling.user>
<sling.password>admin</sling.password>
</properties>

<profiles>
<profile>
<id>auto-deploy</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.sling</groupId>
<artifactId>sling-maven-plugin</artifactId>
<version>2.4.0</version>
<configuration>
<slingUrl>
http://${sling.host}:${sling.port}/apps/sling-observability-weaving-hooks/install/1
</slingUrl>
<deploymentMethod>SlingPostServlet</deploymentMethod>
<user>${sling.user}</user>
<password>${sling.password}</password>
</configuration>
<executions>
<execution>
<id>install-bundle</id>
<goals>
<goal>install</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>

<dependencies>
<!-- OSGi -->
<dependency>
<groupId>org.osgi</groupId>
<artifactId>osgi.core</artifactId>
<version>7.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>osgi.cmpn</artifactId>
<version>7.0.0</version>
<scope>provided</scope>
</dependency>

<!-- Bytecode transformation -->
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.7</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-util</artifactId>
<version>9.7</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-tree</artifactId>
<version>9.7</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-analysis</artifactId>
<version>9.7</version>
<scope>provided</scope>
</dependency>

<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
<scope>provided</scope>
</dependency>

<!-- Testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package be.orbinson.sling.observability.weavinghooks.logmethod;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;

import static org.objectweb.asm.Opcodes.*;

/**
* Method visitor that will add a LoggerFactory log statement call of all parameters when entering a method
*/
public class LogMethodAdapter extends MethodVisitor {

private final String classDescriptor;
private final String methodName;
private final String methodDescriptor;
private final String logLevel;

public LogMethodAdapter(MethodVisitor mv, String classDescriptor, String methodName, String methodDescriptor, String logLevel) {
super(ASM9, mv);
this.classDescriptor = classDescriptor;
this.methodName = methodName;
this.methodDescriptor = methodDescriptor;
this.logLevel = logLevel;
}


@Override
public void visitCode() {
mv.visitVarInsn(ALOAD, 0);
mv.visitLdcInsn(Type.getType(classDescriptor));
mv.visitMethodInsn(INVOKESTATIC, "org/slf4j/LoggerFactory", "getLogger", "(Ljava/lang/Class;)Lorg/slf4j/Logger;", false);
StringBuilder logString = new StringBuilder(methodName);
Type[] types = Type.getArgumentTypes(methodDescriptor);
for (int i = 0; i < types.length; i++) {
logString.append(String.format(", param_%s: <{}>", i + 1));
}
mv.visitLdcInsn(logString.toString());
mv.visitInsn(ICONST_0 + types.length);
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
for (int i = 0; i < types.length; i++) {
mv.visitInsn(DUP);
mv.visitInsn(ICONST_0 + i);
mv.visitVarInsn(ALOAD, i + 1);
mv.visitInsn(AASTORE);
}
mv.visitMethodInsn(INVOKEINTERFACE, "org/slf4j/Logger", logLevel.toLowerCase(), "(Ljava/lang/String;[Ljava/lang/Object;)V", true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package be.orbinson.sling.observability.weavinghooks.logmethod;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* ClassVisitor that will add the {@link LogMethodAdapter} when the method name matches with the requested method name
*/
public class LogMethodClassVisitor extends ClassVisitor {

private final Logger log = LoggerFactory.getLogger(getClass());

private final String methodName;
private final String className;
private final String logLevel;
private String classDescriptor;

public LogMethodClassVisitor(ClassVisitor cv, String className, String methodName, String logLevel) {
super(Opcodes.ASM9, cv);
this.cv = cv;
this.className = className;
this.methodName = methodName;
this.logLevel = logLevel;
}

@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
cv.visit(version, access, name, signature, superName, interfaces);
this.classDescriptor = Type.getObjectType(name).getDescriptor();
}

@Override
public MethodVisitor visitMethod(
int access,
String name,
String desc,
String signature,
String[] exceptions) {

MethodVisitor mv;
mv = cv.visitMethod(access, name, desc, signature, exceptions);
if (mv != null) {
if (name.equals(methodName)) {
log.debug("Adding dynamic log method to class {} and method {}", className, methodName);
mv = new LogMethodAdapter(mv, classDescriptor, methodName, desc, logLevel);
}
}
return mv;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package be.orbinson.sling.observability.weavinghooks.logmethod;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.util.TraceClassVisitor;
import org.osgi.framework.hooks.weaving.WeavingHook;
import org.osgi.framework.hooks.weaving.WovenClass;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.PrintWriter;

public class LogMethodWeavingHook implements WeavingHook {

private final Logger log = LoggerFactory.getLogger(getClass());

private final PrintWriter printWriter = new PrintWriter(System.out);

private final String className;
private final String methodName;
private final String logLevel;
private final boolean enableTraceVisitor;

public LogMethodWeavingHook(LogMethodWeavingHookConfiguration config) {
this.className = config.getClassName();
this.methodName = config.getMethodName();
this.logLevel = config.getLogLevel();
this.enableTraceVisitor = config.isEnableTraceVisitor();
}

@Override
public void weave(WovenClass wovenClass) {
if (wovenClass.getClassName().equals(className)) {
log.debug("Adding dynamic log methods to class {}", wovenClass.getClassName());
addLogMethodToClass(wovenClass);
addDynamicImports(wovenClass);
}
}

private void addLogMethodToClass(WovenClass wovenClass) {
final ClassReader cr = new ClassReader(wovenClass.getBytes());
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
ClassVisitor logMethodClassVisitor;
if (enableTraceVisitor) {
final TraceClassVisitor tcv = new TraceClassVisitor(cw, printWriter);
logMethodClassVisitor = new LogMethodClassVisitor(tcv, className, methodName, logLevel);
} else {
logMethodClassVisitor = new LogMethodClassVisitor(cw, className, methodName, logLevel);
}
cr.accept(logMethodClassVisitor, 0);
wovenClass.setBytes(cw.toByteArray());
}

/**
* Required to add sl4fj as dynamic import if it would not be used in the bundle
*/
private void addDynamicImports(WovenClass wovenClass) {
wovenClass.getDynamicImports().add("org.slf4j");
}

}
Loading

0 comments on commit f0026f0

Please sign in to comment.