-
Notifications
You must be signed in to change notification settings - Fork 6
Extending RemoraJ
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.
Specify Java system property (-d) remora.output
with fully qualified your output path.
I.e. -d remora.output=com.jkoolcloud.remora.core.output.SysOutOutput
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.
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'