Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add trace_methods option #398

Merged
merged 5 commits into from
Jan 7, 2019
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# next

## Features
* Added `trace_methods` configuration option which lets you define which methods in your project or 3rd party libraries should be traced.
To create spans for all `public` methods of classes whose name ends in `Service` which are in a sub-package of `org.example.services` use this matcher:
`public org.example.services.*.*Service#*`

## Bug Fixes

# 1.2.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import co.elastic.apm.agent.bci.bytebuddy.MinimumClassFileVersionValidator;
import co.elastic.apm.agent.bci.bytebuddy.SimpleMethodSignatureOffsetMappingFactory;
import co.elastic.apm.agent.bci.bytebuddy.SoftlyReferencingTypePoolCache;
import co.elastic.apm.agent.bci.methodmatching.MethodMatcher;
import co.elastic.apm.agent.bci.methodmatching.TraceMethodInstrumentation;
import co.elastic.apm.agent.configuration.CoreConfiguration;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.ElasticApmTracerBuilder;
Expand All @@ -44,10 +46,12 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.ServiceLoader;
Expand Down Expand Up @@ -84,7 +88,20 @@ public static void initialize(Instrumentation instrumentation, File agentJarFile
}

public static void initInstrumentation(ElasticApmTracer tracer, Instrumentation instrumentation) {
initInstrumentation(tracer, instrumentation, ServiceLoader.load(ElasticApmInstrumentation.class, ElasticApmInstrumentation.class.getClassLoader()));
initInstrumentation(tracer, instrumentation, loadInstrumentations(tracer));
}

@Nonnull
private static Iterable<ElasticApmInstrumentation> loadInstrumentations(ElasticApmTracer tracer) {
final ArrayList<ElasticApmInstrumentation> instrumentations = new ArrayList<ElasticApmInstrumentation>();
for (ElasticApmInstrumentation instrumentation : ServiceLoader.load(ElasticApmInstrumentation.class, ElasticApmInstrumentation.class.getClassLoader())) {
instrumentations.add(instrumentation);
}
for (MethodMatcher traceMethod : tracer.getConfig(CoreConfiguration.class).getTraceMethods()) {
instrumentations.add(new TraceMethodInstrumentation(traceMethod));
}

return instrumentations;
}

public static void initInstrumentation(final ElasticApmTracer tracer, Instrumentation instrumentation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*/
package co.elastic.apm.agent.bci.bytebuddy;

import co.elastic.apm.agent.matcher.WildcardMatcher;
import com.blogspot.mydailyjava.weaklockfree.WeakConcurrentMap;
import net.bytebuddy.description.NamedElement;
import net.bytebuddy.description.method.MethodDescription;
Expand Down Expand Up @@ -99,4 +100,18 @@ private static boolean canLoadClass(@Nullable ClassLoader target, String classNa
public static MethodHierarchyMatcher overridesOrImplementsMethodThat(ElementMatcher<? super MethodDescription> methodElementMatcher) {
return new MethodHierarchyMatcher(methodElementMatcher);
}

public static ElementMatcher.Junction<NamedElement> matches(final WildcardMatcher matcher) {
return new ElementMatcher.Junction.AbstractBase<NamedElement>() {
@Override
public boolean matches(NamedElement target) {
return matcher.matches(target.getActualName());
}

@Override
public String toString() {
return "matches(" + matcher + ")";
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*-
* #%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.agent.bci.methodmatching;

import co.elastic.apm.agent.matcher.WildcardMatcher;
import org.stagemonitor.util.StringUtils;

import javax.annotation.Nullable;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static co.elastic.apm.agent.matcher.WildcardMatcher.caseSensitiveMatcher;

public class MethodMatcher {

private static final String MODIFIER = "(public|private|protected|\\*)";
private static final String CLASS_NAME = "([a-zA-Z\\d_$.\\*]+)";
private static final String METHOD_NAME = "([a-zA-Z\\d_$\\*]+)";
private static final String PARAM = "([a-zA-Z\\d_$.\\[\\]\\*]+)";
private static final String PARAMS = PARAM + "(,\\s*" + PARAM + ")*";
private static final Pattern METHOD_MATCHER_PATTERN = Pattern.compile("^(" + MODIFIER + "\\s+)?" + CLASS_NAME + "#" + METHOD_NAME + "(\\((" + PARAMS + ")*\\))?$");

private final String stringRepresentation;
@Nullable
private final Integer modifier;
private final WildcardMatcher classMatcher;
private final WildcardMatcher methodMatcher;
@Nullable
private final List<WildcardMatcher> argumentMatchers;

private MethodMatcher(String stringRepresentation, @Nullable Integer modifier, WildcardMatcher classMatcher, WildcardMatcher methodMatcher, @Nullable List<WildcardMatcher> argumentMatchers) {
this.stringRepresentation = stringRepresentation;
this.modifier = modifier;
this.classMatcher = classMatcher;
this.methodMatcher = methodMatcher;
this.argumentMatchers = argumentMatchers;
}

public static MethodMatcher of(String methodMatcher) {
final Matcher matcher = METHOD_MATCHER_PATTERN.matcher(methodMatcher);
if (!matcher.matches()) {
throw new IllegalArgumentException("'" + methodMatcher + "'" + " is not a valid method matcher");
}

return new MethodMatcher(methodMatcher, getModifier(matcher.group(2)), caseSensitiveMatcher(matcher.group(3)), caseSensitiveMatcher(matcher.group(4)),
getArgumentMatchers(matcher.group(5)));
}

@Nullable
private static Integer getModifier(@Nullable String modifier) {
if (modifier == null) {
return null;
}
switch (modifier) {
case "public":
return Modifier.PUBLIC;
case "private":
return Modifier.PRIVATE;
case "protected":
return Modifier.PROTECTED;
default:
return null;
}
}

@Nullable
private static List<WildcardMatcher> getArgumentMatchers(@Nullable String arguments) {
if (arguments == null) {
return null;
}
// remove parenthesis
arguments = arguments.substring(1, arguments.length() - 1);
final String[] splitArguments = StringUtils.split(arguments, ',');
List<WildcardMatcher> matchers = new ArrayList<>(splitArguments.length);
for (String argument : splitArguments) {
matchers.add(caseSensitiveMatcher(argument.trim()));
}
return matchers;
}

public WildcardMatcher getClassMatcher() {
return classMatcher;
}

@Nullable
public Integer getModifier() {
return modifier;
}

public WildcardMatcher getMethodMatcher() {
return methodMatcher;
}

@Nullable
public List<WildcardMatcher> getArgumentMatchers() {
return argumentMatchers;
}

@Override
public String toString() {
return stringRepresentation;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*-
* #%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.agent.bci.methodmatching;

import co.elastic.apm.agent.bci.ElasticApmInstrumentation;
import co.elastic.apm.agent.bci.bytebuddy.SimpleMethodSignatureOffsetMappingFactory;
import co.elastic.apm.agent.impl.transaction.AbstractSpan;
import co.elastic.apm.agent.matcher.WildcardMatcher;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;

import javax.annotation.Nullable;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import static co.elastic.apm.agent.bci.bytebuddy.CustomElementMatchers.matches;
import static net.bytebuddy.matcher.ElementMatchers.declaresMethod;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

public class TraceMethodInstrumentation extends ElasticApmInstrumentation {

protected final MethodMatcher methodMatcher;

public TraceMethodInstrumentation(MethodMatcher methodMatcher) {
this.methodMatcher = methodMatcher;
}

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onMethodEnter(@SimpleMethodSignatureOffsetMappingFactory.SimpleMethodSignature String signature,
@Advice.Local("span") AbstractSpan<?> span) {
if (tracer != null) {
final AbstractSpan<?> parent = tracer.activeSpan();
if (parent == null) {
span = tracer.startTransaction()
.withName(signature)
.activate();
} else {
span = parent.createSpan()
.withName(signature)
.activate();
}
}
}

@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
public static void onMethodExit(@Nullable @Advice.Local("span") AbstractSpan<?> span,
@Advice.Thrown Throwable t) {
if (span != null) {
span.captureException(t)
.deactivate()
.end();
}
}

@Override
public ElementMatcher<? super TypeDescription> getTypeMatcher() {
return matches(methodMatcher.getClassMatcher())
.and(declaresMethod(matches(methodMatcher.getMethodMatcher())));
}

@Override
public ElementMatcher<? super MethodDescription> getMethodMatcher() {
ElementMatcher.Junction<? super MethodDescription> matcher = matches(methodMatcher.getMethodMatcher());
if (methodMatcher.getModifier() != null) {
switch (methodMatcher.getModifier()) {
case Modifier.PUBLIC:
matcher = matcher.and(ElementMatchers.isPublic());
break;
case Modifier.PROTECTED:
matcher = matcher.and(ElementMatchers.isProtected());
break;
case Modifier.PRIVATE:
matcher = matcher.and(ElementMatchers.isPrivate());
break;
}
}
if (methodMatcher.getArgumentMatchers() != null) {
matcher = matcher.and(takesArguments(methodMatcher.getArgumentMatchers().size()));
List<WildcardMatcher> argumentMatchers = methodMatcher.getArgumentMatchers();
for (int i = 0; i < argumentMatchers.size(); i++) {
matcher = matcher.and(takesArgument(i, matches(argumentMatchers.get(i))));
}
}
return matcher;
}

@Override
public Collection<String> getInstrumentationGroupNames() {
return Collections.singletonList("method-matching");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*-
* #%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.agent.bci.methodmatching.configuration;

import co.elastic.apm.agent.bci.methodmatching.MethodMatcher;
import org.stagemonitor.configuration.converter.ValueConverter;

public enum MethodMatcherValueConverter implements ValueConverter<MethodMatcher> {
INSTANCE;

@Override
public MethodMatcher convert(String methodMatcher) throws IllegalArgumentException {
return MethodMatcher.of(methodMatcher);
}

@Override
public String toString(MethodMatcher methodMatcher) {
return methodMatcher.toString();
}

@Override
public String toSafeString(MethodMatcher value) {
return toString(value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*-
* #%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%
*/
@NonnullApi
package co.elastic.apm.agent.bci.methodmatching.configuration;

import co.elastic.apm.agent.annotation.NonnullApi;
Loading