Skip to content

Commit 01edb03

Browse files
committed
add autoconfig
1 parent 2539208 commit 01edb03

File tree

4 files changed

+203
-12
lines changed

4 files changed

+203
-12
lines changed

span-stacktrace/build.gradle.kts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ description = "OpenTelemetry Java span stacktrace capture module"
77
otelJava.moduleName.set("io.opentelemetry.contrib.stacktrace")
88

99
dependencies {
10+
annotationProcessor("com.google.auto.service:auto-service")
11+
compileOnly("com.google.auto.service:auto-service-annotations")
12+
1013
api("io.opentelemetry:opentelemetry-sdk")
1114
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
1215

@@ -16,4 +19,7 @@ dependencies {
1619
testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi")
1720

1821
testImplementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating")
22+
23+
testAnnotationProcessor("com.google.auto.service:auto-service")
24+
testCompileOnly("com.google.auto.service:auto-service-annotations")
1925
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.stacktrace;
7+
8+
import com.google.auto.service.AutoService;
9+
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer;
10+
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
11+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
12+
import io.opentelemetry.sdk.trace.ReadableSpan;
13+
import java.lang.reflect.Constructor;
14+
import java.lang.reflect.InvocationTargetException;
15+
import java.time.Duration;
16+
import java.util.function.Predicate;
17+
import java.util.logging.Level;
18+
import java.util.logging.Logger;
19+
import javax.annotation.Nullable;
20+
21+
@AutoService(AutoConfigurationCustomizerProvider.class)
22+
public class StackTraceAutoConfig implements AutoConfigurationCustomizerProvider {
23+
24+
private static final Logger log = Logger.getLogger(StackTraceAutoConfig.class.getName());
25+
26+
private static final String CONFIG_MIN_DURATION =
27+
"otel.java.experimental.span-stacktrace.min.duration";
28+
private static final Duration CONFIG_MIN_DURATION_DEFAULT = Duration.ofMillis(5);
29+
30+
private static final String CONFIG_FILTER = "otel.java.experimental.span-stacktrace.filter";
31+
32+
@Override
33+
public void customize(AutoConfigurationCustomizer config) {
34+
config.addTracerProviderCustomizer(
35+
(providerBuilder, properties) -> {
36+
long minDuration = getMinDuration(properties);
37+
Predicate<ReadableSpan> filter = getFilterPredicate(properties);
38+
providerBuilder.addSpanProcessor(new StackTraceSpanProcessor(minDuration, filter));
39+
return providerBuilder;
40+
});
41+
}
42+
43+
// package-private for testing
44+
static long getMinDuration(ConfigProperties properties) {
45+
long minDuration =
46+
properties.getDuration(CONFIG_MIN_DURATION, CONFIG_MIN_DURATION_DEFAULT).toNanos();
47+
if (minDuration < 0) {
48+
log.fine("Stack traces capture is disabled");
49+
} else {
50+
log.log(
51+
Level.FINE,
52+
"Stack traces will be added to spans with a minimum duration of {0} nanos",
53+
minDuration);
54+
}
55+
return minDuration;
56+
}
57+
58+
// package private for testing
59+
static Predicate<ReadableSpan> getFilterPredicate(ConfigProperties properties) {
60+
String filterClass = properties.getString(CONFIG_FILTER);
61+
Predicate<ReadableSpan> filter = null;
62+
if (filterClass != null) {
63+
Class<?> filterType = getFilterType(filterClass);
64+
if (filterType != null) {
65+
filter = getFilterInstance(filterType);
66+
}
67+
}
68+
69+
if (filter == null) {
70+
// if value is set, lack of filtering is likely an error and must be reported
71+
Level disabledLogLevel = filterClass != null ? Level.SEVERE : Level.FINE;
72+
log.log(disabledLogLevel, "Span stacktrace filtering disabled");
73+
return span -> true;
74+
} else {
75+
log.fine("Span stacktrace filtering enabled with: " + filterClass);
76+
return filter;
77+
}
78+
}
79+
80+
@Nullable
81+
private static Class<?> getFilterType(String filterClass) {
82+
try {
83+
Class<?> filterType = Class.forName(filterClass);
84+
if (!Predicate.class.isAssignableFrom(filterType)) {
85+
log.severe("Filter must be a subclass of java.util.function.Predicate");
86+
return null;
87+
}
88+
return filterType;
89+
} catch (ClassNotFoundException e) {
90+
log.severe("Unable to load filter class: " + filterClass);
91+
return null;
92+
}
93+
}
94+
95+
@Nullable
96+
@SuppressWarnings("unchecked")
97+
private static Predicate<ReadableSpan> getFilterInstance(Class<?> filterType) {
98+
try {
99+
Constructor<?> constructor = filterType.getConstructor();
100+
return (Predicate<ReadableSpan>) constructor.newInstance();
101+
} catch (NoSuchMethodException
102+
| InstantiationException
103+
| IllegalAccessException
104+
| InvocationTargetException e) {
105+
log.severe("Unable to create filter instance with no-arg constructor: " + filterType);
106+
return null;
107+
}
108+
}
109+
}

span-stacktrace/src/main/java/io/opentelemetry/contrib/stacktrace/StackTraceSpanProcessor.java

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
import java.io.StringWriter;
1616
import java.time.Duration;
1717
import java.util.function.Predicate;
18-
import java.util.logging.Level;
19-
import java.util.logging.Logger;
2018

2119
public class StackTraceSpanProcessor implements ExtendedSpanProcessor {
2220

@@ -28,8 +26,6 @@ public class StackTraceSpanProcessor implements ExtendedSpanProcessor {
2826
private static final AttributeKey<String> SPAN_STACKTRACE =
2927
AttributeKey.stringKey("code.stacktrace");
3028

31-
private static final Logger logger = Logger.getLogger(StackTraceSpanProcessor.class.getName());
32-
3329
private final long minSpanDurationNanos;
3430

3531
private final Predicate<ReadableSpan> filterPredicate;
@@ -42,14 +38,6 @@ public StackTraceSpanProcessor(
4238
long minSpanDurationNanos, Predicate<ReadableSpan> filterPredicate) {
4339
this.minSpanDurationNanos = minSpanDurationNanos;
4440
this.filterPredicate = filterPredicate;
45-
if (minSpanDurationNanos < 0) {
46-
logger.log(Level.FINE, "Stack traces capture is disabled");
47-
} else {
48-
logger.log(
49-
Level.FINE,
50-
"Stack traces will be added to spans with a minimum duration of {0} nanos",
51-
minSpanDurationNanos);
52-
}
5341
}
5442

5543
/**
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.stacktrace;
7+
8+
import static org.assertj.core.api.Assertions.assertThat;
9+
10+
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
11+
import io.opentelemetry.sdk.trace.ReadableSpan;
12+
import java.util.Collections;
13+
import java.util.HashMap;
14+
import java.util.Map;
15+
import java.util.function.Predicate;
16+
import org.junit.jupiter.api.Test;
17+
18+
public class StackTraceAutoConfigTest {
19+
20+
@Test
21+
void defaultConfig() {
22+
DefaultConfigProperties config = DefaultConfigProperties.createFromMap(Collections.emptyMap());
23+
assertThat(StackTraceAutoConfig.getMinDuration(config)).isEqualTo(5000000L);
24+
Predicate<ReadableSpan> filterPredicate = StackTraceAutoConfig.getFilterPredicate(config);
25+
assertThat(filterPredicate).isNotNull();
26+
}
27+
28+
@Test
29+
void minDurationValue() {
30+
Map<String, String> configMap = new HashMap<>();
31+
configMap.put("otel.java.experimental.span-stacktrace.min.duration", "42ms");
32+
DefaultConfigProperties config = DefaultConfigProperties.createFromMap(configMap);
33+
assertThat(StackTraceAutoConfig.getMinDuration(config)).isEqualTo(42000000L);
34+
}
35+
36+
@Test
37+
void negativeMinDuration() {
38+
Map<String, String> configMap = new HashMap<>();
39+
configMap.put("otel.java.experimental.span-stacktrace.min.duration", "-1");
40+
DefaultConfigProperties config = DefaultConfigProperties.createFromMap(configMap);
41+
assertThat(StackTraceAutoConfig.getMinDuration(config)).isNegative();
42+
}
43+
44+
@Test
45+
void customFilter() {
46+
Map<String, String> configMap = new HashMap<>();
47+
configMap.put("otel.java.experimental.span-stacktrace.filter", MyFilter.class.getName());
48+
DefaultConfigProperties config = DefaultConfigProperties.createFromMap(configMap);
49+
Predicate<ReadableSpan> filterPredicate = StackTraceAutoConfig.getFilterPredicate(config);
50+
assertThat(filterPredicate).isInstanceOf(MyFilter.class);
51+
52+
// default does not filter, so any negative value means we use the test filter
53+
assertThat(filterPredicate.test(null)).isFalse();
54+
}
55+
56+
public static class MyFilter implements Predicate<ReadableSpan> {
57+
@Override
58+
public boolean test(ReadableSpan readableSpan) {
59+
return false;
60+
}
61+
}
62+
63+
@Test
64+
void brokenFilter_classVisibility() {
65+
testBrokenFilter(BrokenFilter.class.getName());
66+
}
67+
68+
@Test
69+
void brokenFilter_type() {
70+
testBrokenFilter(Object.class.getName());
71+
}
72+
73+
@Test
74+
void brokenFilter_missingType() {
75+
testBrokenFilter("missing.class.name");
76+
}
77+
78+
private static void testBrokenFilter(String filterName) {
79+
Map<String, String> configMap = new HashMap<>();
80+
configMap.put("otel.java.experimental.span-stacktrace.filter", filterName);
81+
DefaultConfigProperties config = DefaultConfigProperties.createFromMap(configMap);
82+
Predicate<ReadableSpan> filterPredicate = StackTraceAutoConfig.getFilterPredicate(config);
83+
assertThat(filterPredicate).isNotNull();
84+
assertThat(filterPredicate.test(null)).isTrue();
85+
}
86+
87+
private static class BrokenFilter extends MyFilter {}
88+
}

0 commit comments

Comments
 (0)