diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0e477d7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+.classpath
+.project
+.settings/
+target/
+.DS_Store
+.idea/
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d0d36cf
--- /dev/null
+++ b/README.md
@@ -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
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..f7f5d71
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,138 @@
+
+
+
+ 4.0.0
+
+ be.orbinson.sling
+ sling-observability-weavinghooks
+ bundle
+ 0.0.1-SNAPSHOT
+
+ Sling Observability Weaving Hooks
+
+
+
+
+ org.apache.felix
+ maven-bundle-plugin
+ 5.1.9
+ true
+
+ ${basedir}/target/classes
+
+ ${project.artifactId}
+ ${project.version}
+ asm,asm-util,asm-tree,asm-analysis
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 1.8
+ 1.8
+
+
+
+
+
+
+ localhost
+ 8080
+ admin
+ admin
+
+
+
+
+ auto-deploy
+
+
+
+ org.apache.sling
+ sling-maven-plugin
+ 2.4.0
+
+
+ http://${sling.host}:${sling.port}/apps/sling-observability-weaving-hooks/install/1
+
+ SlingPostServlet
+ ${sling.user}
+ ${sling.password}
+
+
+
+ install-bundle
+
+ install
+
+
+
+
+
+
+
+
+
+
+
+
+ org.osgi
+ osgi.core
+ 7.0.0
+ provided
+
+
+ org.osgi
+ osgi.cmpn
+ 7.0.0
+ provided
+
+
+
+
+ org.ow2.asm
+ asm
+ 9.7
+ provided
+
+
+ org.ow2.asm
+ asm-util
+ 9.7
+ provided
+
+
+ org.ow2.asm
+ asm-tree
+ 9.7
+ provided
+
+
+ org.ow2.asm
+ asm-analysis
+ 9.7
+ provided
+
+
+
+
+ org.slf4j
+ slf4j-api
+ 1.7.36
+ provided
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.10.2
+ test
+
+
+
diff --git a/src/main/java/be/orbinson/sling/observability/weavinghooks/logmethod/LogMethodAdapter.java b/src/main/java/be/orbinson/sling/observability/weavinghooks/logmethod/LogMethodAdapter.java
new file mode 100644
index 0000000..36bd16c
--- /dev/null
+++ b/src/main/java/be/orbinson/sling/observability/weavinghooks/logmethod/LogMethodAdapter.java
@@ -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);
+ }
+}
diff --git a/src/main/java/be/orbinson/sling/observability/weavinghooks/logmethod/LogMethodClassVisitor.java b/src/main/java/be/orbinson/sling/observability/weavinghooks/logmethod/LogMethodClassVisitor.java
new file mode 100644
index 0000000..63d0137
--- /dev/null
+++ b/src/main/java/be/orbinson/sling/observability/weavinghooks/logmethod/LogMethodClassVisitor.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/be/orbinson/sling/observability/weavinghooks/logmethod/LogMethodWeavingHook.java b/src/main/java/be/orbinson/sling/observability/weavinghooks/logmethod/LogMethodWeavingHook.java
new file mode 100644
index 0000000..8dd57cb
--- /dev/null
+++ b/src/main/java/be/orbinson/sling/observability/weavinghooks/logmethod/LogMethodWeavingHook.java
@@ -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");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/be/orbinson/sling/observability/weavinghooks/logmethod/LogMethodWeavingHookConfiguration.java b/src/main/java/be/orbinson/sling/observability/weavinghooks/logmethod/LogMethodWeavingHookConfiguration.java
new file mode 100644
index 0000000..d0adf09
--- /dev/null
+++ b/src/main/java/be/orbinson/sling/observability/weavinghooks/logmethod/LogMethodWeavingHookConfiguration.java
@@ -0,0 +1,64 @@
+package be.orbinson.sling.observability.weavinghooks.logmethod;
+
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ConfigurationPolicy;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+@Component(
+ configurationPolicy = ConfigurationPolicy.REQUIRE,
+ service = LogMethodWeavingHookConfiguration.class,
+ immediate = true
+)
+@Designate(
+ ocd = LogMethodWeavingHookConfiguration.Config.class,
+ factory = true
+)
+public class LogMethodWeavingHookConfiguration {
+
+ @ObjectClassDefinition(name = "Sling Observability Weaving Hooks - Log Method Weaving Hook Configuration")
+ @interface Config {
+ @AttributeDefinition(description = "Class name where you want to add a dynamic log method")
+ String className();
+
+ @AttributeDefinition(description = "Method name")
+ String methodName();
+
+ @AttributeDefinition(description = "Log level")
+ String logLevel() default "info";
+
+ @AttributeDefinition(description = "Enable the trace visitor to show what the generated byte code is")
+ boolean enableTraceVisitor() default false;
+ }
+
+ private String className;
+ private String methodName;
+ private String logLevel;
+ private boolean enableTraceVisitor;
+
+ @Activate
+ void activate(Config config) {
+ this.className = config.className();
+ this.methodName = config.methodName();
+ this.logLevel = config.logLevel();
+ this.enableTraceVisitor = config.enableTraceVisitor();
+ }
+
+ public String getClassName() {
+ return className;
+ }
+
+ public String getMethodName() {
+ return methodName;
+ }
+
+ public String getLogLevel() {
+ return logLevel;
+ }
+
+ public boolean isEnableTraceVisitor() {
+ return enableTraceVisitor;
+ }
+}
diff --git a/src/main/java/be/orbinson/sling/observability/weavinghooks/logmethod/LogMethodWeavingHookManager.java b/src/main/java/be/orbinson/sling/observability/weavinghooks/logmethod/LogMethodWeavingHookManager.java
new file mode 100644
index 0000000..71a67b6
--- /dev/null
+++ b/src/main/java/be/orbinson/sling/observability/weavinghooks/logmethod/LogMethodWeavingHookManager.java
@@ -0,0 +1,35 @@
+package be.orbinson.sling.observability.weavinghooks.logmethod;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.hooks.weaving.WeavingHook;
+import org.osgi.service.component.annotations.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Component(immediate = true, service = LogMethodWeavingHookManager.class)
+public class LogMethodWeavingHookManager {
+
+ private final Map> registrations = new HashMap<>();
+ private final BundleContext bundleContext;
+
+ @Activate
+ public LogMethodWeavingHookManager(BundleContext bundleContext) {
+ this.bundleContext = bundleContext;
+ }
+
+ @Reference(service = LogMethodWeavingHookConfiguration.class, cardinality = ReferenceCardinality.MULTIPLE,
+ policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY)
+ void bindLogMethodWeavingHookConfiguration(LogMethodWeavingHookConfiguration config) {
+ LogMethodWeavingHook weavingHook = new LogMethodWeavingHook(config);
+ ServiceRegistration> reg = bundleContext.registerService(WeavingHook.class.getName(), weavingHook, null);
+ registrations.put(config, reg);
+ }
+
+ void unbindLogMethodWeavingHookConfiguration(LogMethodWeavingHookConfiguration config) {
+ ServiceRegistration> reg = registrations.get(config);
+ reg.unregister();
+ registrations.remove(config);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/be/orbinson/sling/observability/weavinghooks/test/ASMifierTester.java b/src/test/java/be/orbinson/sling/observability/weavinghooks/test/ASMifierTester.java
new file mode 100644
index 0000000..19dda29
--- /dev/null
+++ b/src/test/java/be/orbinson/sling/observability/weavinghooks/test/ASMifierTester.java
@@ -0,0 +1,26 @@
+package be.orbinson.sling.observability.weavinghooks.test;
+
+import org.junit.jupiter.api.Test;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.util.ASMifier;
+import org.objectweb.asm.util.TraceClassVisitor;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+public class ASMifierTester {
+
+ /**
+ * Use this method to generate ASM for a specific method
+ **/
+ @Test
+ public void generateASM() throws IOException {
+ String className = "be.orbinson.sling.observability.weavinghooks.test.MyClass";
+
+ PrintWriter output = new PrintWriter(System.out, true);
+ TraceClassVisitor traceClassVisitor = new TraceClassVisitor(null, new ASMifier(), output);
+
+ int parsingOptions = 0;
+ new ClassReader(className).accept(traceClassVisitor, parsingOptions);
+ }
+}
diff --git a/src/test/java/be/orbinson/sling/observability/weavinghooks/test/MyClass.java b/src/test/java/be/orbinson/sling/observability/weavinghooks/test/MyClass.java
new file mode 100644
index 0000000..53ae6ea
--- /dev/null
+++ b/src/test/java/be/orbinson/sling/observability/weavinghooks/test/MyClass.java
@@ -0,0 +1,11 @@
+package be.orbinson.sling.observability.weavinghooks.test;
+
+import org.slf4j.LoggerFactory;
+
+public class MyClass {
+
+ public boolean callMethod(String input, String secondParameter, String thirdParameter) {
+ LoggerFactory.getLogger(MyClass.class).info("{}", new Object[]{input, secondParameter, thirdParameter});
+ return true;
+ }
+}
diff --git a/src/test/java/be/orbinson/sling/observability/weavinghooks/test/TextifierTester.java b/src/test/java/be/orbinson/sling/observability/weavinghooks/test/TextifierTester.java
new file mode 100644
index 0000000..2870342
--- /dev/null
+++ b/src/test/java/be/orbinson/sling/observability/weavinghooks/test/TextifierTester.java
@@ -0,0 +1,26 @@
+package be.orbinson.sling.observability.weavinghooks.test;
+
+import org.junit.jupiter.api.Test;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.util.Textifier;
+import org.objectweb.asm.util.TraceClassVisitor;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+public class TextifierTester {
+
+ /**
+ * Use this method to see the bytecode for a specific class
+ **/
+ @Test
+ public void generateBytecode() throws IOException {
+ String className = "be.orbinson.osgi.log.method.weavinghook.test.MyClass";
+
+ PrintWriter output = new PrintWriter(System.out, true);
+ TraceClassVisitor traceClassVisitor = new TraceClassVisitor(null, new Textifier(), output);
+
+ int parsingOptions = 0;
+ new ClassReader(className).accept(traceClassVisitor, parsingOptions);
+ }
+}