Skip to content

Commit 6fb9796

Browse files
committed
add thread details for declarative config
1 parent 603b737 commit 6fb9796

File tree

3 files changed

+143
-0
lines changed

3 files changed

+143
-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(project(":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: 52 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;
@@ -297,6 +304,7 @@ private Instrumenter<REQUEST, RESPONSE> buildInstrumenter(
297304
InstrumenterConstructor<REQUEST, RESPONSE> constructor,
298305
SpanKindExtractor<? super REQUEST> spanKindExtractor) {
299306

307+
addThreadDetailsAttributeExtractor(this);
300308
applyCustomizers(this);
301309

302310
this.spanKindExtractor = spanKindExtractor;
@@ -435,6 +443,28 @@ public void setSpanNameExtractor(
435443
}
436444
}
437445

446+
private static <RESPONSE, REQUEST> void addThreadDetailsAttributeExtractor(
447+
InstrumenterBuilder<REQUEST, RESPONSE> builder) {
448+
if (builder.openTelemetry instanceof ExtendedOpenTelemetry) {
449+
// Declarative config is used.
450+
// Otherwise, thread details are configured in
451+
// io.opentelemetry.javaagent.tooling.AgentTracerProviderConfigurer.
452+
453+
DeclarativeConfigProperties instrumentationConfig =
454+
((ExtendedOpenTelemetry) builder.openTelemetry)
455+
.getConfigProvider()
456+
.getInstrumentationConfig();
457+
458+
if (instrumentationConfig != null
459+
&& instrumentationConfig
460+
.getStructured("java", empty())
461+
.getStructured("thread_details", empty())
462+
.getBoolean("enabled", false)) {
463+
builder.addAttributesExtractor(new ThreadDetailsAttributesExtractor<>());
464+
}
465+
}
466+
}
467+
438468
private interface InstrumenterConstructor<RQ, RS> {
439469
Instrumenter<RQ, RS> create(InstrumenterBuilder<RQ, RS> builder);
440470

@@ -479,4 +509,26 @@ public <RQ, RS> void propagateOperationListenersToOnEnd(
479509
}
480510
});
481511
}
512+
513+
private static class ThreadDetailsAttributesExtractor<RESPONSE, REQUEST>
514+
implements AttributesExtractor<REQUEST, RESPONSE> {
515+
// attributes are not stable yet
516+
private static final AttributeKey<Long> THREAD_ID = longKey("thread.id");
517+
private static final AttributeKey<String> THREAD_NAME = stringKey("thread.name");
518+
519+
@Override
520+
public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) {
521+
Thread currentThread = Thread.currentThread();
522+
attributes.put(THREAD_ID, currentThread.getId());
523+
attributes.put(THREAD_NAME, currentThread.getName());
524+
}
525+
526+
@Override
527+
public void onEnd(
528+
AttributesBuilder attributes,
529+
Context context,
530+
REQUEST request,
531+
@Nullable RESPONSE response,
532+
@Nullable Throwable error) {}
533+
}
482534
}
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)