Skip to content

Commit 29883b5

Browse files
committed
add thread details for declarative config
1 parent ef21f68 commit 29883b5

File tree

3 files changed

+155
-0
lines changed

3 files changed

+155
-0
lines changed

instrumentation-api/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ dependencies {
2222
testImplementation("io.opentelemetry.javaagent:opentelemetry-testing-common")
2323
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
2424
testImplementation("io.opentelemetry:opentelemetry-exporter-common")
25+
testImplementation("io.opentelemetry:opentelemetry-sdk-extension-incubator")
2526
testImplementation("org.junit-pioneer:junit-pioneer")
2627

2728
jmhImplementation(project(":instrumentation-api-incubator"))

instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,18 @@
55

66
package io.opentelemetry.instrumentation.api.instrumenter;
77

8+
import static io.opentelemetry.api.common.AttributeKey.longKey;
9+
import static io.opentelemetry.api.common.AttributeKey.stringKey;
10+
import static io.opentelemetry.api.incubator.config.DeclarativeConfigProperties.empty;
811
import static java.util.Objects.requireNonNull;
912
import static java.util.logging.Level.WARNING;
1013

1114
import com.google.errorprone.annotations.CanIgnoreReturnValue;
1215
import io.opentelemetry.api.OpenTelemetry;
16+
import io.opentelemetry.api.common.AttributeKey;
17+
import io.opentelemetry.api.common.AttributesBuilder;
18+
import io.opentelemetry.api.incubator.ExtendedOpenTelemetry;
19+
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
1320
import io.opentelemetry.api.metrics.Meter;
1421
import io.opentelemetry.api.metrics.MeterBuilder;
1522
import io.opentelemetry.api.trace.SpanKind;
@@ -54,6 +61,8 @@ public final class InstrumenterBuilder<REQUEST, RESPONSE> {
5461
ConfigPropertiesUtil.getString(
5562
"otel.instrumentation.experimental.span-suppression-strategy"));
5663

64+
static boolean isIncubator = isIncubator();
65+
5766
final OpenTelemetry openTelemetry;
5867
final String instrumentationName;
5968
SpanNameExtractor<? super REQUEST> spanNameExtractor;
@@ -84,6 +93,16 @@ public final class InstrumenterBuilder<REQUEST, RESPONSE> {
8493
operationListenerAttributesExtractor, "operationListenerAttributesExtractor")));
8594
}
8695

96+
private static boolean isIncubator() {
97+
try {
98+
Class.forName("io.opentelemetry.api.incubator.ExtendedOpenTelemetry");
99+
return true;
100+
} catch (ClassNotFoundException e) {
101+
// incubator module is not available
102+
return false;
103+
}
104+
}
105+
87106
InstrumenterBuilder(
88107
OpenTelemetry openTelemetry,
89108
String instrumentationName,
@@ -297,6 +316,7 @@ private Instrumenter<REQUEST, RESPONSE> buildInstrumenter(
297316
InstrumenterConstructor<REQUEST, RESPONSE> constructor,
298317
SpanKindExtractor<? super REQUEST> spanKindExtractor) {
299318

319+
addThreadDetailsAttributeExtractor(this);
300320
applyCustomizers(this);
301321

302322
this.spanKindExtractor = spanKindExtractor;
@@ -446,6 +466,28 @@ public void setSpanNameExtractor(
446466
}
447467
}
448468

469+
private static <RESPONSE, REQUEST> void addThreadDetailsAttributeExtractor(
470+
InstrumenterBuilder<REQUEST, RESPONSE> builder) {
471+
if (isIncubator && builder.openTelemetry instanceof ExtendedOpenTelemetry) {
472+
// Declarative config is used.
473+
// Otherwise, thread details are configured in
474+
// io.opentelemetry.javaagent.tooling.AgentTracerProviderConfigurer.
475+
476+
DeclarativeConfigProperties instrumentationConfig =
477+
((ExtendedOpenTelemetry) builder.openTelemetry)
478+
.getConfigProvider()
479+
.getInstrumentationConfig();
480+
481+
if (instrumentationConfig != null
482+
&& instrumentationConfig
483+
.getStructured("java", empty())
484+
.getStructured("thread_details", empty())
485+
.getBoolean("enabled", false)) {
486+
builder.addAttributesExtractor(new ThreadDetailsAttributesExtractor<>());
487+
}
488+
}
489+
}
490+
449491
private interface InstrumenterConstructor<RQ, RS> {
450492
Instrumenter<RQ, RS> create(InstrumenterBuilder<RQ, RS> builder);
451493

@@ -490,4 +532,26 @@ public <RQ, RS> void propagateOperationListenersToOnEnd(
490532
}
491533
});
492534
}
535+
536+
private static class ThreadDetailsAttributesExtractor<RESPONSE, REQUEST>
537+
implements AttributesExtractor<REQUEST, RESPONSE> {
538+
// attributes are not stable yet
539+
private static final AttributeKey<Long> THREAD_ID = longKey("thread.id");
540+
private static final AttributeKey<String> THREAD_NAME = stringKey("thread.name");
541+
542+
@Override
543+
public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) {
544+
Thread currentThread = Thread.currentThread();
545+
attributes.put(THREAD_ID, currentThread.getId());
546+
attributes.put(THREAD_NAME, currentThread.getName());
547+
}
548+
549+
@Override
550+
public void onEnd(
551+
AttributesBuilder attributes,
552+
Context context,
553+
REQUEST request,
554+
@Nullable RESPONSE response,
555+
@Nullable Throwable error) {}
556+
}
493557
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.api.instrumenter;
7+
8+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
9+
import static java.util.Collections.emptyMap;
10+
import static java.util.Collections.singletonMap;
11+
12+
import io.opentelemetry.api.OpenTelemetry;
13+
import io.opentelemetry.context.Context;
14+
import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfiguration;
15+
import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfigurationBuilder;
16+
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalLanguageSpecificInstrumentationModel;
17+
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.InstrumentationModel;
18+
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel;
19+
import io.opentelemetry.sdk.testing.assertj.SpanDataAssert;
20+
import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension;
21+
import io.opentelemetry.semconv.incubating.ThreadIncubatingAttributes;
22+
import java.lang.reflect.Field;
23+
import java.util.Map;
24+
import java.util.function.Consumer;
25+
import java.util.stream.Stream;
26+
import org.junit.jupiter.api.extension.RegisterExtension;
27+
import org.junit.jupiter.params.ParameterizedTest;
28+
import org.junit.jupiter.params.provider.Arguments;
29+
import org.junit.jupiter.params.provider.MethodSource;
30+
31+
class AddThreadDetailsTest {
32+
33+
@RegisterExtension
34+
static final OpenTelemetryExtension otelTesting = OpenTelemetryExtension.create();
35+
36+
public static Stream<Arguments> allEnabledAndDisabledValues() {
37+
return Stream.of(
38+
Arguments.of(
39+
true,
40+
(Consumer<SpanDataAssert>)
41+
span ->
42+
span.hasAttributesSatisfying(
43+
satisfies(ThreadIncubatingAttributes.THREAD_ID, n -> n.isNotNull()),
44+
satisfies(ThreadIncubatingAttributes.THREAD_NAME, n -> n.isNotBlank()))),
45+
Arguments.of(
46+
false,
47+
(Consumer<SpanDataAssert>)
48+
span ->
49+
span.hasAttributesSatisfying(
50+
satisfies(ThreadIncubatingAttributes.THREAD_ID, n -> n.isNull()),
51+
satisfies(ThreadIncubatingAttributes.THREAD_NAME, n -> n.isNull()))));
52+
}
53+
54+
@ParameterizedTest(name = "enabled={0}")
55+
@MethodSource("allEnabledAndDisabledValues")
56+
void enabled(boolean enabled, Consumer<SpanDataAssert> spanAttributesConsumer)
57+
throws NoSuchFieldException, IllegalAccessException {
58+
OpenTelemetry openTelemetry = DeclarativeConfiguration.create(model(enabled));
59+
Instrumenter<Map<String, String>, Map<String, String>> instrumenter =
60+
Instrumenter.<Map<String, String>, Map<String, String>>builder(
61+
openTelemetry, "test", name -> "span")
62+
.buildInstrumenter();
63+
64+
// OpenTelemetryExtension doesn't allow passing a custom OpenTelemetry instance
65+
Field field = Instrumenter.class.getDeclaredField("tracer");
66+
field.setAccessible(true);
67+
field.set(instrumenter, otelTesting.getOpenTelemetry().getTracer("test"));
68+
69+
Context context = instrumenter.start(Context.root(), emptyMap());
70+
instrumenter.end(context, emptyMap(), emptyMap(), null);
71+
72+
otelTesting
73+
.assertTraces()
74+
.hasTracesSatisfyingExactly(
75+
trace -> trace.hasSpansSatisfyingExactly(spanAttributesConsumer));
76+
}
77+
78+
private static OpenTelemetryConfigurationModel model(boolean enabled) {
79+
return new DeclarativeConfigurationBuilder()
80+
.customizeModel(
81+
new OpenTelemetryConfigurationModel()
82+
.withFileFormat("1.0-rc.1")
83+
.withInstrumentationDevelopment(
84+
new InstrumentationModel()
85+
.withJava(
86+
new ExperimentalLanguageSpecificInstrumentationModel()
87+
.withAdditionalProperty(
88+
"thread_details", singletonMap("enabled", enabled)))));
89+
}
90+
}

0 commit comments

Comments
 (0)