Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
## Features
* Wildcard patterns are now not limited to only one wildcard in the middle and can be arbitrarily complex now.
Example: `*foo*bar*baz`.
* Support for JAX-RS annotations.
Transactions are named based on your resources (`ResourceClass#resourceMethod`).

## Bug Fixes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import co.elastic.apm.bci.bytebuddy.ErrorLoggingListener;
import co.elastic.apm.bci.bytebuddy.MatcherTimer;
import co.elastic.apm.bci.bytebuddy.SimpleMethodSignatureOffsetMappingFactory;
import co.elastic.apm.bci.bytebuddy.SoftlyReferencingTypePoolCache;
import co.elastic.apm.configuration.CoreConfiguration;
import co.elastic.apm.impl.ElasticApmTracer;
Expand All @@ -29,6 +30,7 @@
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.agent.builder.ResettableClassFileTransformer;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.scaffold.MethodGraph;
Expand Down Expand Up @@ -146,6 +148,9 @@ private static AgentBuilder applyAdvice(final ElasticApmTracer tracer, final Age
public boolean matches(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, Class<?> classBeingRedefined, ProtectionDomain protectionDomain) {
long start = System.nanoTime();
try {
if (!advice.getClassLoaderMatcher().matches(classLoader)) {
return false;
}
if (typeMatchingWithNamePreFilter && !advice.getTypeMatcherPreFilter().matches(typeDescription)) {
return false;
}
Expand All @@ -169,7 +174,9 @@ public boolean matches(TypeDescription typeDescription, ClassLoader classLoader,
}
}
})
.transform(new AgentBuilder.Transformer.ForAdvice()
.transform(new AgentBuilder.Transformer.ForAdvice(Advice
.withCustomMapping()
.bind(new SimpleMethodSignatureOffsetMappingFactory()))
.advice(new ElementMatcher<MethodDescription>() {
@Override
public boolean matches(MethodDescription target) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ public ElementMatcher<? super NamedElement> getTypeMatcherPreFilter() {
*/
public abstract ElementMatcher<? super TypeDescription> getTypeMatcher();

public ElementMatcher.Junction<ClassLoader> getClassLoaderMatcher() {
return any();
}

/**
* The method matcher selects methods of types matching {@link #getTypeMatcher()},
* which should be instrumented
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Expand All @@ -23,24 +23,23 @@

public class ClassLoaderNameMatcher extends ElementMatcher.Junction.AbstractBase<ClassLoader> {

private final String name;
private final String name;

private ClassLoaderNameMatcher(String name) {
this.name = name;
}
private ClassLoaderNameMatcher(String name) {
this.name = name;
}

public static ElementMatcher.Junction.AbstractBase<ClassLoader> classLoaderWithName(String name) {
return new ClassLoaderNameMatcher(name);

}
public static ElementMatcher.Junction<ClassLoader> classLoaderWithName(String name) {
return new ClassLoaderNameMatcher(name);
}

public static ElementMatcher.Junction<ClassLoader> isReflectionClassLoader() {
return classLoaderWithName("sun.reflect.DelegatingClassLoader")
.or(classLoaderWithName("jdk.internal.reflect.DelegatingClassLoader"));
}
}

@Override
public boolean matches(ClassLoader target) {
return target != null && name.equals(target.getClass().getName());
}
@Override
public boolean matches(ClassLoader target) {
return target != null && name.equals(target.getClass().getName());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*-
* #%L
* Elastic APM Java agent
* %%
* Copyright (C) 2018 Elastic and contributors
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package co.elastic.apm.bci.bytebuddy;

import com.blogspot.mydailyjava.weaklockfree.WeakConcurrentMap;
import net.bytebuddy.description.NamedElement;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.matcher.ElementMatcher;

import javax.annotation.Nullable;
import java.util.Collection;

import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
import static net.bytebuddy.matcher.ElementMatchers.none;

public class CustomElementMatchers {

public static ElementMatcher.Junction<NamedElement> isInAnyPackage(Collection<String> includedPackages,
ElementMatcher.Junction<NamedElement> defaultIfEmpty) {
if (includedPackages.isEmpty()) {
return defaultIfEmpty;
}
ElementMatcher.Junction<NamedElement> matcher = none();
for (String applicationPackage : includedPackages) {
matcher = matcher.or(nameStartsWith(applicationPackage));
}
return matcher;
}

/**
* Matches only class loaders which can load a certain class.
* <p>
* <b>Warning:</b> the class will be tried to load by each class loader.
* You should choose a class which does not have optional dependencies (imports classes which are not on the class path).
* Ideally, choose an interface or annotation without dependencies.
* </p>
*
* @param className the name of the class to check
* @return a matcher which only matches class loaders which can load a certain class.
*/
public static ElementMatcher.Junction<ClassLoader> classLoaderCanLoadClass(final String className) {
return new ElementMatcher.Junction.AbstractBase<ClassLoader>() {

private final boolean loadableByBootstrapClassLoader = canLoadClass(null, className);
private WeakConcurrentMap<ClassLoader, Boolean> cache = new WeakConcurrentMap.WithInlinedExpunction<>();

@Override
public boolean matches(@Nullable ClassLoader target) {
if (target == null) {
return loadableByBootstrapClassLoader;
}

Boolean result = cache.get(target);
if (result == null) {
result = canLoadClass(target, className);
cache.put(target, result);
}
return result;
}
};
}

private static boolean canLoadClass(@Nullable ClassLoader target, String className) {
boolean result;
try {
Class.forName(className, false, target);
result = true;
} catch (Exception ignore) {
result = false;
}
return result;
}

/**
* Matches overridden methods of a super class or implemented methods of an interface.
* Recursively traverses the superclasses and interfaces.
* The the superclasses and interfaces to examine can be limited via {@link MethodHierarchyMatcher#onSuperClassesThat(ElementMatcher)}.
*
* @param methodElementMatcher The matcher which is applied on the method hierarchy
* @return a matcher which is applied on the method hierarchy
*/
public static MethodHierarchyMatcher overridesOrImplementsMethodThat(ElementMatcher<? super MethodDescription> methodElementMatcher) {
return new MethodHierarchyMatcher(methodElementMatcher);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*-
* #%L
* Elastic APM Java agent
* %%
* Copyright (C) 2018 Elastic and contributors
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package co.elastic.apm.bci.bytebuddy;

import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

import static net.bytebuddy.matcher.ElementMatchers.declaresMethod;
import static net.bytebuddy.matcher.ElementMatchers.is;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.not;
import static net.bytebuddy.matcher.ElementMatchers.returns;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

/**
* This implementation is based on org.stagemonitor.core.instrument.OverridesMethodElementMatcher,
* under Apache License 2.0
*
* @see CustomElementMatchers#overridesOrImplementsMethodThat(ElementMatcher)
*/
public class MethodHierarchyMatcher extends ElementMatcher.Junction.AbstractBase<MethodDescription> {

private final ElementMatcher<? super MethodDescription> extraMethodMatcher;
private final ElementMatcher<? super TypeDescription> superClassMatcher;

MethodHierarchyMatcher(ElementMatcher<? super MethodDescription> extraMethodMatcher) {
this(extraMethodMatcher, not(is(TypeDescription.ForLoadedType.OBJECT)));
}

private MethodHierarchyMatcher(ElementMatcher<? super MethodDescription> extraMethodMatcher, ElementMatcher<? super TypeDescription> superClassMatcher) {
this.extraMethodMatcher = extraMethodMatcher;
this.superClassMatcher = superClassMatcher;
}

public ElementMatcher<MethodDescription> onSuperClassesThat(ElementMatcher<? super TypeDescription> superClassMatcher) {
return new MethodHierarchyMatcher(extraMethodMatcher, superClassMatcher);
}

@Override
public boolean matches(MethodDescription targetMethod) {
return declaresInHierarchy(targetMethod, targetMethod.getDeclaringType().asErasure());
}

private boolean declaresInHierarchy(MethodDescription targetMethod, TypeDescription type) {
if (declaresMethod(named(targetMethod.getName())
.and(returns(targetMethod.getReturnType().asErasure()))
.and(takesArguments(targetMethod.getParameters().asTypeList().asErasures()))
.and(extraMethodMatcher))
.matches(type)) {
return true;
}
for (TypeDescription interfaze : type.getInterfaces().asErasures()) {
if (superClassMatcher.matches(interfaze)) {
if (declaresInHierarchy(targetMethod, interfaze)) {
return true;
}
}
}
final TypeDescription.Generic superClass = type.getSuperClass();
if (superClass != null && superClassMatcher.matches(superClass.asErasure())) {
return declaresInHierarchy(targetMethod, superClass.asErasure());
}
return false;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*-
* #%L
* Elastic APM Java agent
* %%
* Copyright (C) 2018 Elastic and contributors
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package co.elastic.apm.bci.bytebuddy;

import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.ParameterDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.bytecode.assign.Assigner;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;

/**
* Enables using {@link SimpleMethodSignature} in {@link net.bytebuddy.asm.Advice.OnMethodEnter} and
* {@link net.bytebuddy.asm.Advice.OnMethodExit} methods.
*/
public class SimpleMethodSignatureOffsetMappingFactory implements Advice.OffsetMapping.Factory<SimpleMethodSignatureOffsetMappingFactory.SimpleMethodSignature> {

@Override
public Class<SimpleMethodSignature> getAnnotationType() {
return SimpleMethodSignature.class;
}

@Override
public Advice.OffsetMapping make(ParameterDescription.InDefinedShape target,
AnnotationDescription.Loadable<SimpleMethodSignature> annotation,
AdviceType adviceType) {
return new Advice.OffsetMapping() {
@Override
public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner,
Advice.ArgumentHandler argumentHandler, Sort sort) {
final String className = instrumentedMethod.getDeclaringType().getTypeName();
final String simpleClassName = className.substring(className.lastIndexOf('.') + 1);
final String signature = String.format("%s#%s", simpleClassName, instrumentedMethod.getName());
return Target.ForStackManipulation.of(signature);
}
};
}

/**
* Indicates that the annotated parameter should be mapped to a string representation of the instrumented method,
* a constant representing {@link Class#getSimpleName()}{@code #}{@link Method#getName()},
* for example {@code FooClass#barMethod}
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface SimpleMethodSignature {
}

}
Loading