Skip to content

Commit c473016

Browse files
committed
Add thread details for declarative config via span processor
Registers AddThreadDetailsSpanProcessor through declarative config (a named ComponentProvider plus a DeclarativeConfigurationCustomizerProvider that injects the processor node) for both the javaagent and the spring starter. Because it is a SpanProcessor, thread.id/thread.name are added to manually-created spans too, not only Instrumenter-built spans. - agent: enabled via distribution.javaagent (otel.javaagent.add-thread-details) - spring starter: enabled via distribution.spring_starter.thread_details_enabled Alternative to the InstrumenterCustomizer/AttributesExtractor approach in #15209. Signed-off-by: Trask Stalnaker <trask.stalnaker@gmail.com>
1 parent 4593933 commit c473016

17 files changed

Lines changed: 359 additions & 22 deletions

File tree

instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.OtelDisabled;
2323
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.OtelEnabled;
2424
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.OtelMapConverter;
25+
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.thread.ThreadDetailsCustomizerProvider;
2526
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtelResourceProperties;
2627
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtelSpringProperties;
2728
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtlpExporterProperties;
@@ -196,6 +197,11 @@ public DeclarativeConfigurationCustomizerProvider distroConfigurationCustomizerP
196197
return new ResourceCustomizerProvider();
197198
}
198199

200+
@Bean
201+
public DeclarativeConfigurationCustomizerProvider threadDetailsCustomizerProvider() {
202+
return new ThreadDetailsCustomizerProvider();
203+
}
204+
199205
@Bean
200206
public ComponentProvider distroComponentProvider() {
201207
return new DistroComponentProvider();

instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/thread/ThreadDetailsAutoConfiguration.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.thread;
77

88
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation;
9-
import io.opentelemetry.instrumentation.thread.internal.AddThreadDetailsSpanProcessor;
9+
import io.opentelemetry.instrumentation.thread.internal.ThreadDetailsSpanProcessor;
1010
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
1111
import org.springframework.context.annotation.Bean;
1212
import org.springframework.context.annotation.Configuration;
@@ -24,7 +24,7 @@ public AutoConfigurationCustomizerProvider threadDetailOtelCustomizer() {
2424
return p ->
2525
p.addTracerProviderCustomizer(
2626
(builder, config) -> {
27-
builder.addSpanProcessor(new AddThreadDetailsSpanProcessor());
27+
builder.addSpanProcessor(new ThreadDetailsSpanProcessor());
2828
return builder;
2929
});
3030
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.thread;
7+
8+
import io.opentelemetry.instrumentation.thread.internal.AbstractThreadDetailsCustomizerProvider;
9+
import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.DistributionModel;
10+
import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.DistributionPropertyModel;
11+
import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.OpenTelemetryConfigurationModel;
12+
13+
/**
14+
* Adds thread details span attributes when enabled via the {@code distribution.spring_starter}
15+
* node.
16+
*
17+
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
18+
* at any time.
19+
*/
20+
public final class ThreadDetailsCustomizerProvider extends AbstractThreadDetailsCustomizerProvider {
21+
22+
@Override
23+
protected boolean isEnabled(OpenTelemetryConfigurationModel model) {
24+
DistributionModel distribution = model.getDistribution();
25+
if (distribution == null) {
26+
return false;
27+
}
28+
DistributionPropertyModel springStarter =
29+
distribution.getAdditionalProperties().get("spring_starter");
30+
if (springStarter == null) {
31+
return false;
32+
}
33+
Object enabled = springStarter.getAdditionalProperties().get("thread_details_enabled");
34+
if (enabled instanceof Boolean) {
35+
return (Boolean) enabled;
36+
}
37+
// a String when set via environment variable substitution (thread_details_enabled: ${OTEL_...})
38+
return enabled instanceof String && Boolean.parseBoolean((String) enabled);
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.thread;
7+
8+
import static java.nio.charset.StandardCharsets.UTF_8;
9+
import static org.assertj.core.api.Assertions.assertThat;
10+
11+
import io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfiguration;
12+
import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.OpenTelemetryConfigurationModel;
13+
import java.io.ByteArrayInputStream;
14+
import org.junit.jupiter.params.ParameterizedTest;
15+
import org.junit.jupiter.params.provider.CsvSource;
16+
17+
class ThreadDetailsCustomizerProviderTest {
18+
19+
@ParameterizedTest
20+
@CsvSource(
21+
nullValues = "NULL",
22+
value = {
23+
"true, true",
24+
"false, false",
25+
"NULL, false", // distribution node not set
26+
", false", // thread_details_enabled key present with no value
27+
"invalid, false",
28+
})
29+
@SuppressWarnings("StringConcatToTextBlock") // latest dep allows text blocks
30+
void isEnabled(String propertyValue, boolean expected) {
31+
String enabled =
32+
propertyValue == null
33+
? ""
34+
: "distribution:\n"
35+
+ " spring_starter:\n"
36+
+ " thread_details_enabled: "
37+
+ propertyValue
38+
+ "\n";
39+
40+
String yaml = "file_format: \"1.0\"\n" + enabled;
41+
42+
OpenTelemetryConfigurationModel model =
43+
DeclarativeConfiguration.parse(new ByteArrayInputStream(yaml.getBytes(UTF_8)));
44+
45+
assertThat(new ThreadDetailsCustomizerProvider().isEnabled(model)).isEqualTo(expected);
46+
}
47+
}

javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/instrumentation/internal/AgentDistributionConfig.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ public class AgentDistributionConfig {
3939

4040
private final List<String> excludeClassLoaders;
4141

42+
private final boolean threadDetailsEnabled;
43+
4244
private final InstrumentationConfig instrumentation;
4345

4446
public static AgentDistributionConfig get() {
@@ -86,6 +88,7 @@ public static void set(AgentDistributionConfig distributionConfig) {
8688
Boolean forceSynchronousAgentListeners,
8789
@Nullable @JsonProperty("exclude_classes") List<String> excludeClasses,
8890
@Nullable @JsonProperty("exclude_class_loaders") List<String> excludeClassLoaders,
91+
@Nullable @JsonProperty("thread_details_enabled") Boolean threadDetailsEnabled,
8992
@Nullable @JsonProperty("instrumentation") InstrumentationConfig instrumentation) {
9093
this.indyEnabled = indyEnabled != null ? indyEnabled : false;
9194
this.forceSynchronousAgentListeners =
@@ -94,12 +97,13 @@ public static void set(AgentDistributionConfig distributionConfig) {
9497
excludeClasses != null ? new ArrayList<>(excludeClasses) : new ArrayList<>();
9598
this.excludeClassLoaders =
9699
excludeClassLoaders != null ? new ArrayList<>(excludeClassLoaders) : new ArrayList<>();
100+
this.threadDetailsEnabled = threadDetailsEnabled != null ? threadDetailsEnabled : false;
97101
this.instrumentation = instrumentation != null ? instrumentation : new InstrumentationConfig();
98102
}
99103

100104
// Default constructor for testing
101105
AgentDistributionConfig() {
102-
this(null, null, null, null, null);
106+
this(null, null, null, null, null, null);
103107
}
104108

105109
/**
@@ -162,6 +166,10 @@ public boolean isIndyEnabled() {
162166
return indyEnabled;
163167
}
164168

169+
public boolean isThreadDetailsEnabled() {
170+
return threadDetailsEnabled;
171+
}
172+
165173
public boolean isForceSynchronousAgentListeners() {
166174
return forceSynchronousAgentListeners;
167175
}
@@ -227,6 +235,9 @@ private static final class ConfigPropertiesAgentDistributionConfig
227235
"otel.javaagent.experimental.force-synchronous-agent-listeners", false),
228236
configProperties.getList("otel.javaagent.exclude-classes"),
229237
configProperties.getList("otel.javaagent.exclude-class-loaders"),
238+
configProperties.getBoolean(
239+
"otel.javaagent.add-thread-details",
240+
!configProperties.getBoolean("otel.instrumentation.common.v3-preview", false)),
230241
null);
231242
this.configProperties = configProperties;
232243
}

javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentTracerProviderConfigurer.java

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
import com.google.auto.service.AutoService;
1111
import com.google.errorprone.annotations.CanIgnoreReturnValue;
1212
import io.opentelemetry.exporter.logging.LoggingSpanExporter;
13-
import io.opentelemetry.instrumentation.thread.internal.AddThreadDetailsSpanProcessor;
13+
import io.opentelemetry.instrumentation.thread.internal.ThreadDetailsSpanProcessor;
14+
import io.opentelemetry.javaagent.extension.instrumentation.internal.AgentDistributionConfig;
1415
import io.opentelemetry.javaagent.tooling.config.EarlyInitAgentConfig;
1516
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer;
1617
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
@@ -20,8 +21,6 @@
2021

2122
@AutoService(AutoConfigurationCustomizerProvider.class)
2223
public class AgentTracerProviderConfigurer implements AutoConfigurationCustomizerProvider {
23-
private static final String ADD_THREAD_DETAILS = "otel.javaagent.add-thread-details";
24-
2524
@Override
2625
public void customize(AutoConfigurationCustomizer autoConfigurationCustomizer) {
2726
autoConfigurationCustomizer.addTracerProviderCustomizer(
@@ -32,25 +31,19 @@ public void customize(AutoConfigurationCustomizer autoConfigurationCustomizer) {
3231
private static SdkTracerProviderBuilder configure(
3332
SdkTracerProviderBuilder sdkTracerProviderBuilder, ConfigProperties config) {
3433

35-
// Register additional thread details logging span processor
36-
boolean v3Preview = config.getBoolean("otel.instrumentation.common.v3-preview", false);
37-
if (config.getBoolean(ADD_THREAD_DETAILS, !v3Preview)) {
38-
sdkTracerProviderBuilder.addSpanProcessor(new AddThreadDetailsSpanProcessor());
34+
if (AgentDistributionConfig.fromConfigProperties(config).isThreadDetailsEnabled()) {
35+
sdkTracerProviderBuilder.addSpanProcessor(new ThreadDetailsSpanProcessor());
3936
}
4037

41-
maybeEnableLoggingExporter(sdkTracerProviderBuilder, config);
42-
43-
return sdkTracerProviderBuilder;
44-
}
45-
46-
private static void maybeEnableLoggingExporter(
47-
SdkTracerProviderBuilder builder, ConfigProperties config) {
4838
if (EarlyInitAgentConfig.get().isDebug()) {
4939
// don't install another instance if the user has already explicitly requested it.
5040
if (loggingExporterIsNotAlreadyConfigured(config)) {
51-
builder.addSpanProcessor(SimpleSpanProcessor.create(LoggingSpanExporter.create()));
41+
sdkTracerProviderBuilder.addSpanProcessor(
42+
SimpleSpanProcessor.create(LoggingSpanExporter.create()));
5243
}
5344
}
45+
46+
return sdkTracerProviderBuilder;
5447
}
5548

5649
private static boolean loggingExporterIsNotAlreadyConfigured(ConfigProperties config) {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.tooling.config;
7+
8+
import com.google.auto.service.AutoService;
9+
import io.opentelemetry.instrumentation.thread.internal.AbstractThreadDetailsCustomizerProvider;
10+
import io.opentelemetry.javaagent.extension.instrumentation.internal.AgentDistributionConfig;
11+
import io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationCustomizerProvider;
12+
import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.OpenTelemetryConfigurationModel;
13+
14+
/** Adds thread details span attributes when enabled via the {@code distribution.javaagent} node. */
15+
@AutoService(DeclarativeConfigurationCustomizerProvider.class)
16+
public final class ThreadDetailsCustomizerProvider extends AbstractThreadDetailsCustomizerProvider {
17+
18+
@Override
19+
public int order() {
20+
// run after JavaagentDistributionAccessCustomizerProvider (default order) has populated
21+
// AgentDistributionConfig from the distribution.javaagent node
22+
return 1;
23+
}
24+
25+
@Override
26+
protected boolean isEnabled(OpenTelemetryConfigurationModel model) {
27+
return AgentDistributionConfig.get().isThreadDetailsEnabled();
28+
}
29+
}

javaagent-tooling/src/testDistributionConfig/java/io/opentelemetry/javaagent/extension/instrumentation/internal/AgentDistributionConfigTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ void testForceSynchronousAgentListeners() {
3232
assertThat(AgentDistributionConfig.get().isForceSynchronousAgentListeners()).isFalse();
3333
}
3434

35+
@Test
36+
void testThreadDetailsEnabled() {
37+
assertThat(AgentDistributionConfig.get().isThreadDetailsEnabled()).isTrue();
38+
}
39+
3540
@Test
3641
void testExcludeClasses() {
3742
assertThat(AgentDistributionConfig.get().getExcludeClasses())

javaagent-tooling/src/testDistributionConfig/resources/distribution-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ distribution:
99
javaagent:
1010
indy/development: true
1111
force_synchronous_agent_listeners/development: false
12+
thread_details_enabled: true
1213
exclude_classes:
1314
- com.example.excluded.Class1
1415
- com.example.excluded.Class2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.thread.internal;
7+
8+
import io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationCustomizer;
9+
import io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationCustomizerProvider;
10+
import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.OpenTelemetryConfigurationModel;
11+
import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.SpanProcessorModel;
12+
import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.SpanProcessorPropertyModel;
13+
import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.TracerProviderModel;
14+
import java.util.ArrayList;
15+
import java.util.List;
16+
17+
/**
18+
* Adds the {@link ThreadDetailsSpanProcessor} (via the {@link ThreadDetailsComponentProvider}) to
19+
* the declarative configuration model when {@link #isEnabled(OpenTelemetryConfigurationModel)}
20+
* returns {@code true}.
21+
*
22+
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
23+
* at any time.
24+
*/
25+
public abstract class AbstractThreadDetailsCustomizerProvider
26+
implements DeclarativeConfigurationCustomizerProvider {
27+
28+
protected abstract boolean isEnabled(OpenTelemetryConfigurationModel model);
29+
30+
@Override
31+
public void customize(DeclarativeConfigurationCustomizer customizer) {
32+
customizer.addModelCustomizer(
33+
model -> {
34+
maybeAddThreadDetailsProcessor(model);
35+
return model;
36+
});
37+
}
38+
39+
private void maybeAddThreadDetailsProcessor(OpenTelemetryConfigurationModel model) {
40+
if (!isEnabled(model)) {
41+
return;
42+
}
43+
TracerProviderModel tracerProvider = model.getTracerProvider();
44+
if (tracerProvider == null) {
45+
tracerProvider = new TracerProviderModel();
46+
model.withTracerProvider(tracerProvider);
47+
}
48+
List<SpanProcessorModel> processors = tracerProvider.getProcessors();
49+
if (processors == null) {
50+
processors = new ArrayList<>();
51+
tracerProvider.withProcessors(processors);
52+
}
53+
processors.add(
54+
new SpanProcessorModel()
55+
.withAdditionalProperty(
56+
ThreadDetailsComponentProvider.NAME, new SpanProcessorPropertyModel()));
57+
}
58+
}

0 commit comments

Comments
 (0)