Skip to content

Extending RemoraJ

Albert edited this page Jun 3, 2020 · 5 revisions

Creating Outputs

Extending the class com.jkoolcloud.remora.core.output.AgentOutput one can effectively create his own outputs for variuos data storage and/or transfer needs.

Filter output interface:

package com.jkoolcloud.remora.core.output;
public interface AgentOutput<T> {
	void init() throws OutputException;
	void send(T entry);
	void shutdown();

There are init(), send(), shudtdown() methods to implement.

The method send() on method interception will be executed twice: on method enter, and on method exit. If its not the behavior you seek you can check EntryDefinition.isFinished()`.

Method init() will be called on initialization, and shudtdown() on vm exit.

Attaching Output

Specify Java system property (-d) remora.output with fully qualified your output path. I.e. -d remora.output=com.jkoolcloud.remora.core.output.SysOutOutput

Creating Advices

There is prepared maven archetype remora-archetype. You need to install this using maven. Run cmd in archetype folder mvn install first.

Remora-archetype will create template for advice.

Run newModule.bat on source root folder. The wizard will be asking you how the new module should be named. It should be named as remora-<name>. The fallowing question is how the advice class shoud be named, it shoud be named fallowing common Java guidelines starting with capital letter. Suffix Advice will be added.

New module will create source, test and register Advice in src\main\resources\META-INF\services\com.jkoolcloud.remora.advices.RemoraAdvice. You can create multiple advice classes, but you need to add full class name to services\com.jkoolcloud.remora.advices.RemoraAdvice manually.

The field ADVICE_NAME determines the advice name. This is how the log file is named, usually it`s the same as class simpleName.

The field INTERCEPTING_CLASS is for selecting the class needs for the instrumentation. For more sophisticated class control override getTypeMatcher() method.

The field INTERCEPTING_METHOD if for selecting method to be instrumented. For more sophisticated control ovveride method methodMatcher().

@Advice.OnMethodEnter
public static void before(@Advice.This Object thiz, //
	@Advice.AllArguments Object[] arguments, //
	@Advice.Origin Method method, //
	@Advice.Local("ed") EntryDefinition ed, //
	@Advice.Local("startTime") long startTime) {
	try {
		if (!intercept(MyAdvice.class, thiz, method, arguments)) {
			return;
		}
		ed = getEntryDefinition(ed, MyAdvice.class, logging ? logger : null);;
		if (logging) {
			logger.info("Entering: {} {}",MyAdvice.class.getName(), "before"));
		}
		startTime = fillDefaultValuesBefore(ed, stackThreadLocal, thiz, method, logging ? logger : null);
	} catch (Throwable t) {
		handleAdviceException(t, ADVICE_NAME, logging ? logger : null  );
	}
}

The instrumented method will be altered including the code of advice's before() before original method code. Parameters: thiz - pass the reference to the instrumented class object method - the instrumented method itself arguments - arguments passed to original method ed - remora created EntryDefinition of the instrumented method, should be null on before, this parameter will pass EntryDefinition to after method. starttime - start time of instrumented method.

The line intercept(MyAdvice.class, thiz, method, arguments) will apply required filters and return() if filtered.

ed = getEntryDefinition() will create(if required) or get the Entity of intercepted method. Call to fillDefaultValuesBefore() will fill the EntryDefinition with default values.

All the interception code should be wrapped with:

try {

} catch (Throwable t) {
	handleAdviceException();
}

The instrumented method will be altered including the code of advice's after() after original method code.

@Advice.OnMethodExit(onThrowable = Throwable.class)
public static void after(@Advice.This Object thiz, //
		@Advice.Origin Method method, //
		@Advice.AllArguments Object[] arguments, //
		// @Advice.Return Object returnValue, // 
		@Advice.Thrown Throwable exception, @Advice.Local("ed") EntryDefinition ed, //
		@Advice.Local("startTime") long startTime) {
	boolean doFinally = true;
	try {
		if (!intercept(${adviceClassName}Advice.class, thiz, method, arguments)) {
			return;
		}
		if (ed == null) { // ed expected to be null if not created by entry, that's for duplicates
			if (logging) {
				logger.info("EntryDefinition not exist, entry might be filtered out as duplicate or ran on test");
			}
			doFinally = false;
			return;
		}
		if (logging) {
			logger.info("Exiting: {} {}",${adviceClassName}Advice.class.getName(), "after"));
		}
		fillDefaultValuesAfter(ed, startTime, exception, logging ? logger : null );
	} catch (Throwable t) {
		handleAdviceException(t, ADVICE_NAME, logging ? logger : null  );
	} finally {
		if (doFinally) {
			doFinally(logging ? logger : null, obj.getClass());
		}
	}
}

Parameters: thiz - pass the reference to the instrumented class object method - the instrumented method itself arguments - arguments passed to original method return value - value returned by method, should't be used if any of methods intercepted return void exception - exception thrown by method ed - remora created EntryDefinition of the instrumented method, should be null on before, this parameter will pass EntryDefinition to after method. starttime - start time of instrumented method.

Creating Filters

Advice filter interface:

package com.jkoolcloud.remora.filters;
public interface AdviceFilter {
	boolean maches(Object thiz, Method method, Object... arguments);
	Mode getMode();
}

mode - filter mode INCLUDE/EXCLUDE, exclude will filter out defined interception, include will only pass defined interception. The default method intercept() will be called from advice, it check the mode and returns true if the advice needed to process the method interception further.

All public fields marked for @RemoraConfig.Configurable will be filled either the configuration or remora-control REST request. Default values should be provided.

Example implementation:

public class ClassNameFilter implements AdviceFilter {
	@RemoraConfig.Configurable
	public List<String> classNames;
	@RemoraConfig.Configurable
	public Mode mode = Mode.EXCLUDE;

	@Override
	public boolean maches(Object thiz, Method method, Object... arguments) {
		return classNames.contains(thiz.getClass().getName());
	}

	@Override
	public Mode getMode() {
		return mode;
	}
}

Example filter can be configured from remora.properties i.e.:

filter.ingnoredStreams.type=com.jkoolcloud.remora.filters.ClassNameFilter
filter.ingnoredStreams.mode=EXCLUDE
filter.ingnoredStreams.classNames=java.net.SocketInputStream;\
  java.util.jar.JarVerifier$VerifierStream;

or using remora-control REST request:

curl -XPOST -d '{
                "class": "com.jkoolcloud.remora.filters.ClassNameFilter",
                "name": "test",
                "classNames": "com.test;com.test2",
                "mode": "EXCLUDE"
                }' 'http://localhost:7366/filters'
Clone this wiki locally