Skip to content

Commit 279d8b4

Browse files
authored
Add annotations to create transactions and spans (#281)
closes #212
1 parent 8acd694 commit 279d8b4

File tree

26 files changed

+625
-83
lines changed

26 files changed

+625
-83
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
# 0.9.0
1+
# next
22

33
## Breaking changes
44
* Remove intake v1 support. This version requires an APM Server which supports the intake api v2.
55

66
## Features
7+
* Adds `@CaptureTransaction` and `@CaptureSpan` annotations which let you declaratively add custom transactions and spans.
8+
Note that it is required to configure the `application_packages` for this to work.
79

810
## Bug Fixes
911

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*-
2+
* #%L
3+
* Elastic APM Java agent
4+
* %%
5+
* Copyright (C) 2018 Elastic and contributors
6+
* %%
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* #L%
19+
*/
20+
package co.elastic.apm.api;
21+
22+
/**
23+
* Annotating a method with {@code @}{@link CaptureSpan} creates a {@link Span} as the child of the currently active span or transaction
24+
* ({@link ElasticApm#currentSpan()}).
25+
* <p>
26+
* When there is no current span,
27+
* no span will be created.
28+
* </p>
29+
* <p>
30+
* Note: it is required to configure the {@code application_packages}, otherwise this annotation will be ignored.
31+
* </p>
32+
*/
33+
public @interface CaptureSpan {
34+
35+
/**
36+
* The name of the {@link Span}.
37+
* Defaults to the {@code ClassName#methodName}
38+
*/
39+
String value() default "";
40+
41+
/**
42+
* Sets the type of span.
43+
* <p>
44+
* The type is a hierarchical string used to group similar spans together.
45+
* For instance, all spans of MySQL queries are given the type `db.mysql.query`.
46+
* </p>
47+
* <p>
48+
* In the above example `db` is considered the type prefix. Though there are no naming restrictions for this prefix,
49+
* the following are standardized across all Elastic APM agents: `app`, `db`, `cache`, `template`, and `ext`.
50+
* </p>
51+
*/
52+
String type() default "app";
53+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*-
2+
* #%L
3+
* Elastic APM Java agent
4+
* %%
5+
* Copyright (C) 2018 Elastic and contributors
6+
* %%
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* #L%
19+
*/
20+
package co.elastic.apm.api;
21+
22+
/**
23+
* Annotating a method with {@code @}{@link CaptureTransaction} creates a {@link Transaction} for that method.
24+
* <p>
25+
* Note that this only works when there is no active transaction on the same thread.
26+
* </p>
27+
* <p>
28+
* Note: it is required to configure the {@code application_packages}, otherwise this annotation will be ignored.
29+
* </p>
30+
*/
31+
public @interface CaptureTransaction {
32+
33+
/**
34+
* The name of the {@link Transaction}.
35+
* Defaults to the {@code ClassName#methodName}
36+
*/
37+
String value() default "";
38+
39+
/**
40+
* The type of the transaction.
41+
*/
42+
String type() default Transaction.TYPE_REQUEST;
43+
}

apm-agent-core/src/main/java/co/elastic/apm/bci/ElasticApmAgent.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
*/
2020
package co.elastic.apm.bci;
2121

22+
import co.elastic.apm.bci.bytebuddy.AnnotationValueOffsetMappingFactory;
2223
import co.elastic.apm.bci.bytebuddy.ErrorLoggingListener;
2324
import co.elastic.apm.bci.bytebuddy.MatcherTimer;
2425
import co.elastic.apm.bci.bytebuddy.SimpleMethodSignatureOffsetMappingFactory;
@@ -176,7 +177,8 @@ public boolean matches(TypeDescription typeDescription, ClassLoader classLoader,
176177
})
177178
.transform(new AgentBuilder.Transformer.ForAdvice(Advice
178179
.withCustomMapping()
179-
.bind(new SimpleMethodSignatureOffsetMappingFactory()))
180+
.bind(new SimpleMethodSignatureOffsetMappingFactory())
181+
.bind(new AnnotationValueOffsetMappingFactory()))
180182
.advice(new ElementMatcher<MethodDescription>() {
181183
@Override
182184
public boolean matches(MethodDescription target) {
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*-
2+
* #%L
3+
* Elastic APM Java agent
4+
* %%
5+
* Copyright (C) 2018 Elastic and contributors
6+
* %%
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* #L%
19+
*/
20+
package co.elastic.apm.bci.bytebuddy;
21+
22+
import net.bytebuddy.asm.Advice;
23+
import net.bytebuddy.description.annotation.AnnotationDescription;
24+
import net.bytebuddy.description.method.MethodDescription;
25+
import net.bytebuddy.description.method.ParameterDescription;
26+
import net.bytebuddy.description.type.TypeDescription;
27+
import net.bytebuddy.implementation.bytecode.assign.Assigner;
28+
29+
import javax.annotation.Nullable;
30+
import java.lang.annotation.ElementType;
31+
import java.lang.annotation.Retention;
32+
import java.lang.annotation.RetentionPolicy;
33+
import java.lang.annotation.Target;
34+
35+
import static net.bytebuddy.matcher.ElementMatchers.named;
36+
37+
public class AnnotationValueOffsetMappingFactory implements Advice.OffsetMapping.Factory<AnnotationValueOffsetMappingFactory.AnnotationValueExtractor> {
38+
39+
@Override
40+
public Class<AnnotationValueExtractor> getAnnotationType() {
41+
return AnnotationValueExtractor.class;
42+
}
43+
44+
@Override
45+
public Advice.OffsetMapping make(final ParameterDescription.InDefinedShape target,
46+
final AnnotationDescription.Loadable<AnnotationValueExtractor> annotation,
47+
final AdviceType adviceType) {
48+
return new Advice.OffsetMapping() {
49+
@Override
50+
public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Advice.ArgumentHandler argumentHandler, Sort sort) {
51+
return Target.ForStackManipulation.of(getAnnotationValue(instrumentedMethod, annotation.loadSilent()));
52+
}
53+
};
54+
}
55+
56+
@Nullable
57+
private Object getAnnotationValue(MethodDescription instrumentedMethod, AnnotationValueExtractor annotationValueExtractor) {
58+
for (TypeDescription typeDescription : instrumentedMethod.getDeclaredAnnotations().asTypeList()) {
59+
if (named(annotationValueExtractor.annotationClassName()).matches(typeDescription)) {
60+
for (MethodDescription.InDefinedShape annotationMethod : typeDescription.getDeclaredMethods()) {
61+
if (annotationMethod.getName().equals(annotationValueExtractor.method())) {
62+
return instrumentedMethod.getDeclaredAnnotations().ofType(typeDescription).getValue(annotationMethod).resolve();
63+
}
64+
}
65+
}
66+
}
67+
return null;
68+
}
69+
70+
@Retention(RetentionPolicy.RUNTIME)
71+
@Target(ElementType.PARAMETER)
72+
public @interface AnnotationValueExtractor {
73+
String annotationClassName();
74+
75+
String method();
76+
}
77+
78+
}

apm-agent-core/src/main/java/co/elastic/apm/bci/bytebuddy/SimpleMethodSignatureOffsetMappingFactory.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import net.bytebuddy.description.type.TypeDescription;
2727
import net.bytebuddy.implementation.bytecode.assign.Assigner;
2828

29+
import javax.annotation.Nullable;
30+
import java.lang.annotation.Annotation;
2931
import java.lang.annotation.ElementType;
3032
import java.lang.annotation.Retention;
3133
import java.lang.annotation.RetentionPolicy;
@@ -52,7 +54,8 @@ public Advice.OffsetMapping make(ParameterDescription.InDefinedShape target,
5254
public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner,
5355
Advice.ArgumentHandler argumentHandler, Sort sort) {
5456
final String className = instrumentedMethod.getDeclaringType().getTypeName();
55-
final String simpleClassName = className.substring(className.lastIndexOf('.') + 1);
57+
String simpleClassName = className.substring(className.lastIndexOf('$') + 1);
58+
simpleClassName = simpleClassName.substring(simpleClassName.lastIndexOf('.') + 1);
5659
final String signature = String.format("%s#%s", simpleClassName, instrumentedMethod.getName());
5760
return Target.ForStackManipulation.of(signature);
5861
}
@@ -69,4 +72,5 @@ public Target resolve(TypeDescription instrumentedType, MethodDescription instru
6972
public @interface SimpleMethodSignature {
7073
}
7174

75+
7276
}

apm-agent-core/src/main/java/co/elastic/apm/impl/stacktrace/StacktraceConfiguration.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ public class StacktraceConfiguration extends ConfigurationOptionProvider {
3535
.key(APPLICATION_PACKAGES)
3636
.configurationCategory(STACKTRACE_CATEGORY)
3737
.description("Used to determine whether a stack trace frame is an 'in-app frame' or a 'library frame'.\n" +
38-
"Setting this option can also improve the startup time.")
38+
"Setting this option can also improve the startup time.\n" +
39+
"\n" +
40+
"In order to be able to use the API annotations @CaptureTransaction and @CaptureSpan,\n" +
41+
"it is required to set these options.")
3942
.dynamic(true)
4043
.buildWithDefault(Collections.<String>emptyList());
4144

apm-agent-core/src/test/java/co/elastic/apm/AbstractInstrumentationTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,18 @@
2424
import co.elastic.apm.impl.ElasticApmTracer;
2525
import co.elastic.apm.impl.ElasticApmTracerBuilder;
2626
import net.bytebuddy.agent.ByteBuddyAgent;
27+
import org.junit.After;
2728
import org.junit.AfterClass;
2829
import org.junit.Before;
2930
import org.junit.BeforeClass;
3031
import org.junit.jupiter.api.AfterAll;
32+
import org.junit.jupiter.api.AfterEach;
3133
import org.junit.jupiter.api.BeforeAll;
3234
import org.junit.jupiter.api.BeforeEach;
3335
import org.stagemonitor.configuration.ConfigurationRegistry;
3436

37+
import static org.assertj.core.api.Assertions.assertThat;
38+
3539
public abstract class AbstractInstrumentationTest {
3640
protected static ElasticApmTracer tracer;
3741
protected static MockReporter reporter;
@@ -77,4 +81,10 @@ public static ConfigurationRegistry getConfig() {
7781
public final void resetReporter() {
7882
reset();
7983
}
84+
85+
@After
86+
@AfterEach
87+
public final void cleanUp() {
88+
assertThat(tracer.getActive()).isNull();
89+
}
8090
}

apm-agent-core/src/test/resources/elasticapm.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ log_level=DEBUG
22
log_file=System.out
33
# incubating instrumentations should be active in tests
44
disable_instrumentations=
5+
application_packages=co.elastic.apm
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*-
2+
* #%L
3+
* Elastic APM Java agent
4+
* %%
5+
* Copyright (C) 2018 Elastic and contributors
6+
* %%
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* #L%
19+
*/
20+
package co.elastic.apm.plugin.api;
21+
22+
import co.elastic.apm.bci.ElasticApmInstrumentation;
23+
24+
import java.util.Collection;
25+
import java.util.Collections;
26+
27+
import static co.elastic.apm.plugin.api.ElasticApmApiInstrumentation.PUBLIC_API_INSTRUMENTATION_GROUP;
28+
29+
public abstract class ApiInstrumentation extends ElasticApmInstrumentation {
30+
@Override
31+
public boolean includeWhenInstrumentationIsDisabled() {
32+
return true;
33+
}
34+
35+
@Override
36+
public Collection<String> getInstrumentationGroupNames() {
37+
return Collections.singleton(PUBLIC_API_INSTRUMENTATION_GROUP);
38+
}
39+
}

0 commit comments

Comments
 (0)