Skip to content

Commit 2cb456c

Browse files
committed
support micrometer observed annotation
1 parent 328077c commit 2cb456c

11 files changed

+233
-78
lines changed

src/main/java/org/digma/DigmaAgent.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,20 @@ private static void startAgent(Instrumentation inst, boolean fromPremain) {
4747
}
4848

4949

50-
if (configuration.isExtendedObservabilityEnabled()) {
51-
WithSpanTransformer.install(inst);
50+
if (configuration.isExtendedObservabilityByNamespaceEnabled()) {
51+
Log.debug("Extended observability by namespace is configured, installing WithSpan transformer.");
52+
new ExtendedObservabilityByNamespace().install(inst);
5253
agentActivated = true;
5354
} else {
54-
Log.debug("Extended observability is not configured, not installing WithSpan transformer.");
55+
Log.debug("Extended observability by namespace is not configured, not installing WithSpan transformer.");
56+
}
57+
58+
if (configuration.isExtendedObservabilityByAnnotationEnabled()) {
59+
Log.debug("Extended observability by annotation is configured, installing WithSpan transformer.");
60+
new ExtendedObservabilityByAnnotation().install(inst);
61+
agentActivated = true;
62+
} else {
63+
Log.debug("Extended observability by annotation is not configured, not installing WithSpan transformer.");
5564
}
5665

5766

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package org.digma;
2+
3+
import net.bytebuddy.asm.MemberAttributeExtension;
4+
import net.bytebuddy.description.annotation.AnnotationDescription;
5+
import net.bytebuddy.description.method.MethodDescription;
6+
import net.bytebuddy.description.type.TypeDescription;
7+
import net.bytebuddy.dynamic.DynamicType;
8+
import net.bytebuddy.matcher.ElementMatcher;
9+
import org.digma.configuration.Configuration;
10+
11+
import static net.bytebuddy.matcher.ElementMatchers.*;
12+
import static org.digma.OtelClassNames.WITH_SPAN_CLASS_NAME;
13+
14+
public class ExtendedObservabilityByAnnotation extends WithSpanTransformer {
15+
16+
17+
@Override
18+
protected ElementMatcher<? super TypeDescription> getTypeMatcher() {
19+
20+
ElementMatcher.Junction<? super TypeDescription> hasAnnotatedMethodsMatcher = none();
21+
for (String annotationName : Configuration.getInstance().getMethodsAnnotations()) {
22+
hasAnnotatedMethodsMatcher = hasAnnotatedMethodsMatcher.or(declaresMethod(isAnnotatedWith(named(annotationName))));
23+
}
24+
25+
return hasAnnotatedMethodsMatcher;
26+
}
27+
28+
@Override
29+
protected ElementMatcher<? super MethodDescription> getMethodMatcher(TypeDescription typeDescription) {
30+
ElementMatcher.Junction<MethodDescription> annotatedMethodsMatcher = none();
31+
for (String annotationName : Configuration.getInstance().getMethodsAnnotations()) {
32+
annotatedMethodsMatcher = annotatedMethodsMatcher.or(isAnnotatedWith(named(annotationName)));
33+
}
34+
35+
return isMethod().and(isDeclaredBy(typeDescription))
36+
.and(not(isAnnotatedWith(named(WITH_SPAN_CLASS_NAME))))
37+
.and(annotatedMethodsMatcher);
38+
}
39+
40+
@Override
41+
protected DynamicType.Builder<?> annotate(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader) throws Exception {
42+
//the WithSpan annotation should be loadable from the same class loader of the application class
43+
AnnotationDescription withSpanAnnotationDescription = getWithSpanAnnotationDescription(classLoader);
44+
45+
return builder
46+
.visit(new MemberAttributeExtension.ForMethod()
47+
.annotateMethod(withSpanAnnotationDescription)
48+
.on(getMethodMatcher(typeDescription)));
49+
}
50+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package org.digma;
2+
3+
import net.bytebuddy.asm.MemberAttributeExtension;
4+
import net.bytebuddy.description.annotation.AnnotationDescription;
5+
import net.bytebuddy.description.method.MethodDescription;
6+
import net.bytebuddy.description.type.TypeDescription;
7+
import net.bytebuddy.dynamic.DynamicType;
8+
import net.bytebuddy.matcher.ElementMatcher;
9+
import org.digma.configuration.Configuration;
10+
11+
public class ExtendedObservabilityByNamespace extends WithSpanTransformer {
12+
13+
@Override
14+
protected ElementMatcher<? super TypeDescription> getTypeMatcher() {
15+
return NamespaceTypeMatchers.create(Configuration.getInstance());
16+
}
17+
18+
@Override
19+
protected ElementMatcher<? super MethodDescription> getMethodMatcher(TypeDescription typeDescription) {
20+
return NamespaceMethodMatchers.create(typeDescription, Configuration.getInstance());
21+
}
22+
23+
@Override
24+
protected DynamicType.Builder<?> annotate(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader) throws Exception {
25+
26+
//the WithSpan annotation should be loadable from the same class loader of the application class
27+
AnnotationDescription withSpanAnnotationDescription = getWithSpanAnnotationDescription(classLoader);
28+
29+
AnnotationDescription digmaMarkerAnnotationDescription = getDigmaMarkerAnnotationDescription();
30+
31+
return builder
32+
.visit(new MemberAttributeExtension.ForMethod()
33+
.annotateMethod(withSpanAnnotationDescription)
34+
.annotateMethod(digmaMarkerAnnotationDescription)
35+
.on(getMethodMatcher(typeDescription)));
36+
}
37+
}

src/main/java/org/digma/MethodMatchers.java renamed to src/main/java/org/digma/NamespaceMethodMatchers.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
import static org.digma.OtelClassNames.SPAN_ATTRIBUTE_CLASS_NAME;
1717
import static org.digma.OtelClassNames.WITH_SPAN_CLASS_NAME;
1818

19-
public class MethodMatchers {
19+
public class NamespaceMethodMatchers {
2020

21-
private MethodMatchers() {
21+
private NamespaceMethodMatchers() {
2222
}
2323

2424
public static ElementMatcher<? super MethodDescription> create(TypeDescription typeDescription, Configuration configuration) {
@@ -48,6 +48,7 @@ public static ElementMatcher<? super MethodDescription> create(TypeDescription t
4848

4949
return isMethod()
5050
.and(isDeclaredBy(typeDescription))
51+
.and(not(isAnnotatedWith(named(WITH_SPAN_CLASS_NAME))))
5152
.and(not(excludeNamesMatcher))
5253
.and(not(isSetter()))
5354
.and(not(isGetter()))

src/main/java/org/digma/TypeMatchers.java renamed to src/main/java/org/digma/NamespaceTypeMatchers.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
import static net.bytebuddy.matcher.ElementMatchers.*;
1111
import static org.digma.Matchers.getNamedElementJunction;
1212

13-
public class TypeMatchers {
13+
public class NamespaceTypeMatchers {
1414

15-
private TypeMatchers() {
15+
private NamespaceTypeMatchers() {
1616
}
1717

1818
public static ElementMatcher<? super TypeDescription> create(Configuration configuration) {

src/main/java/org/digma/OtelApiInjector.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,22 @@
1313
public class OtelApiInjector {
1414

1515
private final static String OTEL_JARS_RESOURCE_FOLDER = "/otelJars";
16+
private static boolean alreadyInjected = false;
1617

1718
public static void injectOtelApiJarToSystemClassLoader() throws IOException {
19+
20+
Log.debug("injectOtelApiJarToSystemClassLoader called");
21+
22+
//this method may be called multiple times, but we need to inject only once
23+
if (alreadyInjected){
24+
Log.debug("not injecting otel api to system class loader because already injected");
25+
return;
26+
}
27+
28+
alreadyInjected = true;
29+
30+
Log.debug("injecting otel api to system class loader");
31+
1832
List<JarFile> jarFiles = createOtelJarFiles();
1933
for (JarFile jarFile : jarFiles) {
2034
Log.debug("injecting to system class loader , jar file:" + jarFile.getName());

src/main/java/org/digma/WithSpanTransformer.java

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
import net.bytebuddy.ByteBuddy;
44
import net.bytebuddy.agent.builder.AgentBuilder;
5-
import net.bytebuddy.asm.MemberAttributeExtension;
65
import net.bytebuddy.description.annotation.AnnotationDescription;
6+
import net.bytebuddy.description.method.MethodDescription;
7+
import net.bytebuddy.description.type.TypeDescription;
8+
import net.bytebuddy.dynamic.DynamicType;
9+
import net.bytebuddy.matcher.ElementMatcher;
710
import org.digma.configuration.Configuration;
811
import org.digma.instrumentation.ExtendedObservability;
912
import org.digma.matchers.NotGeneratedClassMatcher;
@@ -13,15 +16,25 @@
1316

1417
import static org.digma.OtelClassNames.WITH_SPAN_CLASS_NAME;
1518

16-
public class WithSpanTransformer {
19+
public abstract class WithSpanTransformer {
1720

1821

19-
public static void install(Instrumentation inst) {
22+
protected abstract ElementMatcher<? super TypeDescription> getTypeMatcher();
23+
24+
protected abstract ElementMatcher<? super MethodDescription> getMethodMatcher(TypeDescription typeDescription);
25+
26+
//the different implementation may need to annotate different annotations.
27+
//for example in ExtendedObservabilityByAnnotation we don't need the org.digma.instrumentation.ExtendedObservability
28+
protected abstract DynamicType.Builder<?> annotate(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader) throws Exception;
29+
30+
31+
public void install(Instrumentation inst) {
2032

2133
Log.debug("Extended observability is enabled, installing WithSpan transformer.");
2234
Log.debug("Digma agent started with extended observability configuration: " +
2335
"includePackages=" + Configuration.getInstance().getIncludePackages()
24-
+ ",excludeNames=" + Configuration.getInstance().getExcludeNames());
36+
+ ",excludeNames=" + Configuration.getInstance().getExcludeNames()
37+
+ ",methodsAnnotations=" + Configuration.getInstance().getMethodsAnnotations());
2538

2639

2740
try {
@@ -56,7 +69,7 @@ public static void install(Instrumentation inst) {
5669
}
5770

5871

59-
private static void makeSureWithSpanClassIsAvailable() {
72+
private void makeSureWithSpanClassIsAvailable() {
6073

6174
//if configuration is true inject and return
6275
if (Configuration.getInstance().shouldInjectOtelApiToSystemClassLoader()) {
@@ -86,7 +99,7 @@ private static void makeSureWithSpanClassIsAvailable() {
8699
}
87100

88101

89-
private static void injectOtelApiToSystemClassLoader() {
102+
private void injectOtelApiToSystemClassLoader() {
90103
if (Configuration.getInstance().shouldInjectOtelApiToSystemClassLoaderExist() &&
91104
!Configuration.getInstance().shouldInjectOtelApiToSystemClassLoader()) {
92105
Log.debug("injectOtelApiToSystemClassLoader was called but configuration is false, not injecting");
@@ -107,28 +120,21 @@ private static void injectOtelApiToSystemClassLoader() {
107120
}
108121

109122

110-
private static void installTransformer(Instrumentation inst) {
123+
private void installTransformer(Instrumentation inst) {
111124

112125
Log.debug("installing withSpanTransformer");
113126

114127
new AgentBuilder.Default()
115-
.type(TypeMatchers.create(Configuration.getInstance()))
128+
.type(getTypeMatcher())
116129
.and(new NotGeneratedClassMatcher())
117130
.transform((builder, typeDescription, classLoader, module, protectionDomain) -> {
118131

119132
try {
120-
//the WithSpan annotation should be loadable from the same class loader of the application class
121-
AnnotationDescription withSpanAnnotationDescription = getWithSpanAnnotationDescription(classLoader);
122-
123-
AnnotationDescription digmaMarkerAnnotationDescription = getDigmaMarkerAnnotationDescription();
124133

125134
Log.debug("transforming " + typeDescription.getCanonicalName() + " in class loader " + classLoader);
126135

127-
return builder
128-
.visit(new MemberAttributeExtension.ForMethod()
129-
.annotateMethod(withSpanAnnotationDescription)
130-
.annotateMethod(digmaMarkerAnnotationDescription)
131-
.on(MethodMatchers.create(typeDescription, Configuration.getInstance())));
136+
return annotate(builder, typeDescription, classLoader);
137+
132138
} catch (Throwable e) {
133139
Log.error("got exception in bytebuddy transformer", e);
134140
return builder;
@@ -139,7 +145,7 @@ private static void installTransformer(Instrumentation inst) {
139145

140146

141147
@SuppressWarnings("unchecked")
142-
private static AnnotationDescription getWithSpanAnnotationDescription(ClassLoader classLoader) throws Exception {
148+
public static AnnotationDescription getWithSpanAnnotationDescription(ClassLoader classLoader) throws Exception {
143149

144150
// if (classLoader.getResource(WITH_SPAN_CLASS_NAME.replace('.', '/') + ".class") == null) {
145151
// Log.debug("class loader "+classLoader+" doesn't have WithSpan class resource, trying to inject to system class loader");
@@ -155,7 +161,7 @@ private static AnnotationDescription getWithSpanAnnotationDescription(ClassLoade
155161
}
156162

157163

158-
private static AnnotationDescription getDigmaMarkerAnnotationDescription() {
164+
public static AnnotationDescription getDigmaMarkerAnnotationDescription() {
159165

160166
Log.debug("trying to load ExtendedObservability annotation class");
161167
AnnotationDescription annotationDescription = AnnotationDescription.Latent.Builder.ofType(ExtendedObservability.class).build();

src/main/java/org/digma/configuration/Configuration.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.util.List;
88
import java.util.stream.Collectors;
99

10+
@SuppressWarnings("DuplicatedCode")
1011
public class Configuration {
1112

1213
public static final String OS_NAME = System.getProperty("os.name");
@@ -48,6 +49,17 @@ public class Configuration {
4849
public static final String DIGMA_AUTO_INSTRUMENT_PACKAGES_EXCLUDE_NAMES_SYSTEM_PROPERTY = "digma.autoinstrument.packages.exclude.names";
4950
public static final String DIGMA_AUTO_INSTRUMENT_PACKAGES_EXCLUDE_NAMES_ENV_VAR = "DIGMA_AUTOINSTRUMENT_PACKAGES_EXCLUDE_NAMES";
5051

52+
53+
/**
54+
* a list of annotations names.
55+
* the agent will instrument any method that is annotation with any of these annotations.
56+
* for example micrometer io.micrometer.observation.annotation.Observed
57+
*/
58+
public static final String DIGMA_AUTO_INSTRUMENT_METHODS_BY_ANNOTATION_SYSTEM_PROPERTY = "digma.autoinstrument.methods.by.annotation";
59+
public static final String DIGMA_AUTO_INSTRUMENT_METHODS_BY_ANNOTATION_ENV_VAR = "DIGMA_AUTOINSTRUMENT_METHODS_BY_ANNOTATION";
60+
61+
62+
5163
/**
5264
* argument to control injection of otel api to system class loader.
5365
* if it doesn't exist the agent will check if @WithSpan exists in the system class loader, if not, it will inject otel api to the system class loader.
@@ -71,6 +83,8 @@ public class Configuration {
7183

7284
private final List<String> includePackages;
7385
private final List<String> excludeNames;
86+
private final List<String> methodsAnnotations;
87+
7488

7589
private static final Configuration INSTANCE = new Configuration(new ConfigurationReader());
7690
private final ConfigurationReader configurationReader;
@@ -94,6 +108,7 @@ public class Configuration {
94108
this.configurationReader = configurationReader;
95109
includePackages = loadExtendedObservabilityPackages();
96110
excludeNames = loadExcludeNames();
111+
methodsAnnotations = loadMethodsAnnotations();
97112
}
98113

99114

@@ -127,10 +142,14 @@ public boolean isDebug() {
127142
return getBoolean(DEBUG_SYSTEM_PROPERTY, DEBUG_ENV_VAR);
128143
}
129144

130-
public boolean isExtendedObservabilityEnabled(){
145+
public boolean isExtendedObservabilityByNamespaceEnabled(){
131146
return !getIncludePackages().isEmpty();
132147
}
133148

149+
public boolean isExtendedObservabilityByAnnotationEnabled(){
150+
return !getMethodsAnnotations().isEmpty();
151+
}
152+
134153

135154
private boolean isExposePreparedStatementsParametersExist() {
136155
return configurationReader.getEnvOrSystemProperty(JDBC_PS_PARAMS_ENABLED_SYSTEM_PROPERTY) != null ||
@@ -148,6 +167,11 @@ public List<String> getIncludePackages() {
148167
return includePackages;
149168
}
150169

170+
@NotNull
171+
public List<String> getMethodsAnnotations() {
172+
return methodsAnnotations;
173+
}
174+
151175
@NotNull
152176
public List<String> getExcludeNames() {
153177
return excludeNames;
@@ -168,6 +192,20 @@ private List<String> loadExtendedObservabilityPackages() {
168192
}
169193
}
170194

195+
@NotNull
196+
private List<String> loadMethodsAnnotations() {
197+
198+
String annotations = configurationReader.getEnvOrSystemProperty(DIGMA_AUTO_INSTRUMENT_METHODS_BY_ANNOTATION_SYSTEM_PROPERTY);
199+
if (annotations == null) {
200+
annotations = configurationReader.getEnvOrSystemProperty(DIGMA_AUTO_INSTRUMENT_METHODS_BY_ANNOTATION_ENV_VAR);
201+
}
202+
if (annotations != null) {
203+
return Arrays.asList(annotations.split(";"));
204+
} else {
205+
return Collections.emptyList();
206+
}
207+
}
208+
171209
@NotNull
172210
private List<String> loadExcludeNames() {
173211
String excludeNames = configurationReader.getEnvOrSystemProperty(DIGMA_AUTO_INSTRUMENT_PACKAGES_EXCLUDE_NAMES_SYSTEM_PROPERTY);

0 commit comments

Comments
 (0)