diff --git a/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceConfigInstrumentation.java b/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceConfigInstrumentation.java index 0412676e344..97411c6104f 100644 --- a/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceConfigInstrumentation.java +++ b/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceConfigInstrumentation.java @@ -2,7 +2,16 @@ import static datadog.trace.agent.tooling.ClassLoaderMatcher.hasClassesNamed; import static datadog.trace.agent.tooling.bytebuddy.matcher.DDElementMatchers.safeHasSuperType; +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; +import static net.bytebuddy.matcher.ElementMatchers.isEquals; +import static net.bytebuddy.matcher.ElementMatchers.isFinalizer; +import static net.bytebuddy.matcher.ElementMatchers.isGetter; +import static net.bytebuddy.matcher.ElementMatchers.isHashCode; +import static net.bytebuddy.matcher.ElementMatchers.isSetter; +import static net.bytebuddy.matcher.ElementMatchers.isSynthetic; +import static net.bytebuddy.matcher.ElementMatchers.isToString; import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; import com.google.auto.service.AutoService; import datadog.trace.agent.tooling.Instrumenter; @@ -33,7 +42,8 @@ public class TraceConfigInstrumentation implements Instrumenter { static final String PACKAGE_CLASS_NAME_REGEX = "[\\w.\\$]+"; - private static final String METHOD_LIST_REGEX = "\\s*(?:\\w+\\s*,)*\\s*(?:\\w+\\s*,?)\\s*"; + private static final String METHOD_LIST_REGEX = + "\\s*(?:\\*|(?:\\w+\\s*,)*\\s*(?:\\w+\\s*,?))\\s*"; private static final String CONFIG_FORMAT = "(?:\\s*" + PACKAGE_CLASS_NAME_REGEX @@ -63,7 +73,7 @@ public TraceConfigInstrumentation() { } else if (!validateConfigString(configString)) { log.warn( - "Invalid trace method config '{}'. Must match 'package.Class$Name[method1,method2];*'.", + "Invalid trace method config '{}'. Must match 'package.Class$Name[method1,method2];*' or 'package.Class$Name[*];*'.", configString); classMethodsToTrace = Collections.emptyMap(); @@ -147,7 +157,20 @@ public Map, String> transformers() { ElementMatcher.Junction methodMatchers = null; for (final String methodName : methodNames) { if (methodMatchers == null) { - methodMatchers = named(methodName); + if (methodName.equals("*")) { + methodMatchers = + not( + isHashCode() + .or(isEquals()) + .or(isToString()) + .or(isFinalizer()) + .or(isGetter()) + .or(isConstructor()) + .or(isSetter()) + .or(isSynthetic())); + } else { + methodMatchers = named(methodName); + } } else { methodMatchers = methodMatchers.or(named(methodName)); } diff --git a/dd-java-agent/instrumentation/trace-annotation/src/test/groovy/TraceConfigTest.groovy b/dd-java-agent/instrumentation/trace-annotation/src/test/groovy/TraceConfigTest.groovy index c1b7d5a203f..832f04f9c4d 100644 --- a/dd-java-agent/instrumentation/trace-annotation/src/test/groovy/TraceConfigTest.groovy +++ b/dd-java-agent/instrumentation/trace-annotation/src/test/groovy/TraceConfigTest.groovy @@ -9,7 +9,7 @@ class TraceConfigTest extends AgentTestRunner { @Override void configurePreAgent() { super.configurePreAgent() - injectSysConfig("dd.trace.methods", "package.ClassName[method1,method2];${ConfigTracedCallable.name}[call]") + injectSysConfig("dd.trace.methods", "package.ClassName[method1,method2];${ConfigTracedCallable.name}[call];${ConfigTracedCallable2.name}[*];${Animal.name}[animalSound];${DictionaryElement.name}[*];${Floor.name}[setNumber];${Mammal.name}[*]") } class ConfigTracedCallable implements Callable { @@ -19,18 +19,207 @@ class TraceConfigTest extends AgentTestRunner { } } + class ConfigTracedCallable2 implements Callable { + int g + + ConfigTracedCallable2(){ + g = 4 + } + @Override + String call() throws Exception { + return call_helper() + } + + String call_helper() throws Exception { + return "Hello2!" + } + + String getValue() { + return "hello" + } + void setValue(int value) { + g = value + } + } + + interface Mammal { + void name(String newName) + void height(int newHeight) + } + + interface Floor { + void setNumber() + } + + class Fifth implements Floor { + int floorNumber + + void setNumber() { + floorNumber = 5 + } + } + + class Human implements Mammal { + String name + String height + + void name(String newName){ + name = newName + } + void height(int newHeight){ + height = newHeight + } + } + + + abstract class Animal { + abstract void animalSound() + void sleep() { + System.out.println("Zzz") + } + } + + abstract class DictionaryElement{ + abstract void produceDefinition() + } + + class Sophisticated extends DictionaryElement{ + void produceDefinition() { + System.out.println("of such excellence, grandeur, or beauty as to inspire great admiration or awe.") + } + } + class Pig extends Animal { + void animalSound() { + System.out.println("The pig says: wee wee") + } + } + def "test configuration based trace"() { - expect: + when: new ConfigTracedCallable().call() == "Hello!" + new Pig().animalSound() + then: + assertTraces(2) { + trace(1) { + span { + resourceName "ConfigTracedCallable.call" + operationName "trace.annotation" + tags { + "$Tags.COMPONENT" "trace" + defaultTags() + } + } + } + trace(1) { + span { + resourceName "Pig.animalSound" + operationName "trace.annotation" + tags { + "$Tags.COMPONENT" "trace" + defaultTags() + } + } + } + } + } + def "test wildcard configuration"() { when: - TEST_WRITER.waitForTraces(1) + ConfigTracedCallable2 object = new ConfigTracedCallable2() + object.call() + object.hashCode() + object == new ConfigTracedCallable2() + object.toString() + object.finalize() + object.getValue() + object.setValue(5) + new Sophisticated().produceDefinition() + then: - assertTraces(1) { + assertTraces(2) { + trace(2) { + span { + resourceName "ConfigTracedCallable2.call" + operationName "trace.annotation" + tags { + "$Tags.COMPONENT" "trace" + defaultTags() + } + } + span { + resourceName "ConfigTracedCallable2.call_helper" + operationName "trace.annotation" + tags { + "$Tags.COMPONENT" "trace" + defaultTags() + } + } + } trace(1) { span { - resourceName "ConfigTracedCallable.call" + resourceName "Sophisticated.produceDefinition" + operationName "trace.annotation" + tags { + "$Tags.COMPONENT" "trace" + defaultTags() + } + } + } + } + } + + def "test wildcard configuration with class implementing interface"() { + + when: + Human charlie = new Human() + charlie.name("Charlie") + charlie.height(4) + + then: + assertTraces(2) { + trace(1) { + span { + resourceName "Human.name" + operationName "trace.annotation" + tags { + "$Tags.COMPONENT" "trace" + defaultTags() + } + } + } + trace(1) { + span { + resourceName "Human.height" + operationName "trace.annotation" + tags { + "$Tags.COMPONENT" "trace" + defaultTags() + } + } + } + } + } + def "test wildcard configuration based on class extending abstract class"() { + + when: + new Pig().animalSound() + new Fifth().setNumber() + then: + assertTraces(2) { + trace(1) { + span { + resourceName "Pig.animalSound" + operationName "trace.annotation" + tags { + "$Tags.COMPONENT" "trace" + defaultTags() + } + } + } + trace(1) { + span { + resourceName "Fifth.setNumber" operationName "trace.annotation" tags { "$Tags.COMPONENT" "trace" @@ -64,5 +253,9 @@ class TraceConfigTest extends AgentTestRunner { "ClassName[method1 , method2]" | ["ClassName": ["method1", "method2"].toSet()] "Class\$1[method1 ] ; Class\$2[ method2];" | ["Class\$1": ["method1"].toSet(), "Class\$2": ["method2"].toSet()] "Duplicate[method1] ; Duplicate[method2] ;Duplicate[method3];" | ["Duplicate": ["method3"].toSet()] + "ClassName[*]" | ["ClassName": ["*"].toSet()] + "ClassName[*,asdfg]" | [:] + "ClassName[asdfg,*]" | [:] + "Class[*] ; Class\$2[ method2];" | ["Class": ["*"].toSet(), "Class\$2": ["method2"].toSet()] } }