Skip to content

Commit eb8f3d6

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 eb8f3d6

13 files changed

Lines changed: 451 additions & 15 deletions

File tree

docs/advanced-configuration-options.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,18 @@ We plan to integrate OpenTelemetry's own client-side monitoring solution by defa
5555
- The snippet is injected only into HTML responses that contain a `<head>` tag
5656
- The agent will attempt to preserve the original character encoding of the response
5757
- If the response already has a `Content-Length` header, it will be updated to reflect the additional content
58+
59+
## Thread details span attributes
60+
61+
This option controls whether the javaagent adds the experimental `thread.id` and `thread.name`
62+
span attributes.
63+
64+
| System property | Environment variable | Purpose |
65+
| --------------------------------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
66+
| otel.javaagent.add-thread-details | OTEL_JAVAAGENT_ADD_THREAD_DETAILS | Enable capture of experimental `thread.id` and `thread.name` span attributes on spans created by the javaagent and manual spans. |
67+
68+
**Important notes:**
69+
70+
- The default is `true`
71+
- When `otel.instrumentation.common.v3-preview=true`, the default becomes `false`
72+
- An explicit value for `otel.javaagent.add-thread-details` overrides that preview-based default

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();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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+
return enabled instanceof String && Boolean.parseBoolean((String) enabled);
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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.api.incubator.config.DeclarativeConfigProperties;
12+
import io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfiguration;
13+
import io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationCustomizer;
14+
import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.OpenTelemetryConfigurationModel;
15+
import io.opentelemetry.sdk.logs.export.LogRecordExporter;
16+
import io.opentelemetry.sdk.metrics.export.MetricExporter;
17+
import io.opentelemetry.sdk.trace.export.SpanExporter;
18+
import java.io.ByteArrayInputStream;
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
import java.util.function.BiFunction;
22+
import java.util.function.Function;
23+
import org.junit.jupiter.params.ParameterizedTest;
24+
import org.junit.jupiter.params.provider.CsvSource;
25+
26+
class ThreadDetailsCustomizerProviderTest {
27+
28+
@ParameterizedTest
29+
@CsvSource(
30+
nullValues = "NULL",
31+
value = {
32+
"true, true",
33+
"false, false",
34+
"NULL, false", // distribution node not set
35+
", false", // thread_details_enabled key present with no value
36+
"invalid, false",
37+
})
38+
@SuppressWarnings("StringConcatToTextBlock") // latest dep allows text blocks
39+
void addsThreadDetailsProcessor(String propertyValue, boolean expected) {
40+
String enabled =
41+
propertyValue == null
42+
? ""
43+
: "distribution:\n"
44+
+ " spring_starter:\n"
45+
+ " thread_details_enabled: "
46+
+ propertyValue
47+
+ "\n";
48+
49+
String yaml = "file_format: \"1.0\"\n" + enabled;
50+
51+
OpenTelemetryConfigurationModel model =
52+
applyCustomizer(
53+
DeclarativeConfiguration.parse(new ByteArrayInputStream(yaml.getBytes(UTF_8))),
54+
new ThreadDetailsCustomizerProvider());
55+
56+
assertThat(threadDetailsProcessorPresent(model)).isEqualTo(expected);
57+
}
58+
59+
private static boolean threadDetailsProcessorPresent(OpenTelemetryConfigurationModel model) {
60+
if (model.getTracerProvider() == null || model.getTracerProvider().getProcessors() == null) {
61+
return false;
62+
}
63+
return model.getTracerProvider().getProcessors().stream()
64+
.anyMatch(processor -> processor.getAdditionalProperties().containsKey("thread_details"));
65+
}
66+
67+
private static OpenTelemetryConfigurationModel applyCustomizer(
68+
OpenTelemetryConfigurationModel model, ThreadDetailsCustomizerProvider provider) {
69+
List<Function<OpenTelemetryConfigurationModel, OpenTelemetryConfigurationModel>> customizers =
70+
new ArrayList<>();
71+
provider.customize(new ModelCustomizerCollector(customizers));
72+
for (Function<OpenTelemetryConfigurationModel, OpenTelemetryConfigurationModel> customizer :
73+
customizers) {
74+
model = customizer.apply(model);
75+
}
76+
return model;
77+
}
78+
79+
private static class ModelCustomizerCollector implements DeclarativeConfigurationCustomizer {
80+
private final List<Function<OpenTelemetryConfigurationModel, OpenTelemetryConfigurationModel>>
81+
customizers;
82+
83+
ModelCustomizerCollector(
84+
List<Function<OpenTelemetryConfigurationModel, OpenTelemetryConfigurationModel>>
85+
customizers) {
86+
this.customizers = customizers;
87+
}
88+
89+
@Override
90+
public void addModelCustomizer(
91+
Function<OpenTelemetryConfigurationModel, OpenTelemetryConfigurationModel> customizer) {
92+
customizers.add(customizer);
93+
}
94+
95+
@Override
96+
public <T extends SpanExporter> void addSpanExporterCustomizer(
97+
Class<T> exporterType, BiFunction<T, DeclarativeConfigProperties, T> customizer) {}
98+
99+
@Override
100+
public <T extends MetricExporter> void addMetricExporterCustomizer(
101+
Class<T> exporterType, BiFunction<T, DeclarativeConfigProperties, T> customizer) {}
102+
103+
@Override
104+
public <T extends LogRecordExporter> void addLogRecordExporterCustomizer(
105+
Class<T> exporterType, BiFunction<T, DeclarativeConfigProperties, T> customizer) {}
106+
}
107+
}

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: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.google.errorprone.annotations.CanIgnoreReturnValue;
1212
import io.opentelemetry.exporter.logging.LoggingSpanExporter;
1313
import io.opentelemetry.instrumentation.thread.internal.AddThreadDetailsSpanProcessor;
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)) {
34+
if (AgentDistributionConfig.fromConfigProperties(config).isThreadDetailsEnabled()) {
3835
sdkTracerProviderBuilder.addSpanProcessor(new AddThreadDetailsSpanProcessor());
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+
}

0 commit comments

Comments
 (0)