Skip to content

Commit 23f7b19

Browse files
authored
Add inherited attributes preview (#1854)
* Add inherited attributes preview * Add smoke test
1 parent 4906847 commit 23f7b19

File tree

15 files changed

+520
-9
lines changed

15 files changed

+520
-9
lines changed

agent/agent-tooling/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,13 @@ dependencies {
6262

6363
testImplementation("org.junit.jupiter:junit-jupiter")
6464
testImplementation("org.assertj:assertj-core")
65+
testImplementation("org.awaitility:awaitility")
6566
testImplementation("org.mockito:mockito-core")
6667
testImplementation("uk.org.webcompere:system-stubs-jupiter:1.1.0")
6768
testImplementation("io.github.hakky54:logcaptor")
6869

70+
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
71+
6972
testImplementation("com.microsoft.jfr:jfr-streaming")
7073
testImplementation("com.azure:azure-storage-blob")
7174
}

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/configuration/Configuration.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import com.microsoft.applicationinsights.agent.bootstrap.diagnostics.DiagnosticsHelper;
3131
import com.microsoft.applicationinsights.agent.bootstrap.diagnostics.status.StatusFile;
3232
import com.microsoft.applicationinsights.agent.internal.common.FriendlyException;
33+
import io.opentelemetry.api.common.AttributeKey;
3334
import java.util.ArrayList;
3435
import java.util.HashMap;
3536
import java.util.List;
@@ -195,11 +196,59 @@ public static class PreviewConfiguration {
195196
public LiveMetrics liveMetrics = new LiveMetrics();
196197
public LegacyRequestIdPropagation legacyRequestIdPropagation = new LegacyRequestIdPropagation();
197198

199+
public List<InheritedAttribute> inheritedAttributes = new ArrayList<>();
200+
198201
public ProfilerConfiguration profiler = new ProfilerConfiguration();
199202
public GcEventConfiguration gcEvents = new GcEventConfiguration();
200203
public AadAuthentication authentication = new AadAuthentication();
201204
}
202205

206+
public static class InheritedAttribute {
207+
public String key;
208+
public SpanAttributeType type;
209+
210+
public AttributeKey<?> getAttributeKey() {
211+
switch (type) {
212+
case STRING:
213+
return AttributeKey.stringKey(key);
214+
case BOOLEAN:
215+
return AttributeKey.booleanKey(key);
216+
case LONG:
217+
return AttributeKey.longKey(key);
218+
case DOUBLE:
219+
return AttributeKey.doubleKey(key);
220+
case STRING_ARRAY:
221+
return AttributeKey.stringArrayKey(key);
222+
case BOOLEAN_ARRAY:
223+
return AttributeKey.booleanArrayKey(key);
224+
case LONG_ARRAY:
225+
return AttributeKey.longArrayKey(key);
226+
case DOUBLE_ARRAY:
227+
return AttributeKey.doubleArrayKey(key);
228+
}
229+
throw new IllegalStateException("Unexpected attribute key type: " + type);
230+
}
231+
}
232+
233+
public enum SpanAttributeType {
234+
@JsonProperty("string")
235+
STRING,
236+
@JsonProperty("boolean")
237+
BOOLEAN,
238+
@JsonProperty("long")
239+
LONG,
240+
@JsonProperty("double")
241+
DOUBLE,
242+
@JsonProperty("string-array")
243+
STRING_ARRAY,
244+
@JsonProperty("boolean-array")
245+
BOOLEAN_ARRAY,
246+
@JsonProperty("long-array")
247+
LONG_ARRAY,
248+
@JsonProperty("double-array")
249+
DOUBLE_ARRAY
250+
}
251+
203252
public static class LegacyRequestIdPropagation {
204253
public boolean enabled;
205254
}
@@ -904,6 +953,7 @@ public void validate() {
904953
}
905954

906955
public enum AuthenticationType {
956+
// TODO (kyralama) should these use @JsonProperty to bind lowercase like other enums?
907957
UAMI,
908958
SAMI,
909959
VSCODE,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* ApplicationInsights-Java
3+
* Copyright (c) Microsoft Corporation
4+
* All rights reserved.
5+
*
6+
* MIT License
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
8+
* software and associated documentation files (the ""Software""), to deal in the Software
9+
* without restriction, including without limitation the rights to use, copy, modify, merge,
10+
* publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11+
* persons to whom the Software is furnished to do so, subject to the following conditions:
12+
* The above copyright notice and this permission notice shall be included in all copies or
13+
* substantial portions of the Software.
14+
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15+
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16+
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17+
* FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18+
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19+
* DEALINGS IN THE SOFTWARE.
20+
*/
21+
22+
package com.microsoft.applicationinsights.agent.internal.init;
23+
24+
import com.microsoft.applicationinsights.agent.internal.configuration.Configuration;
25+
import io.opentelemetry.api.common.AttributeKey;
26+
import io.opentelemetry.api.trace.Span;
27+
import io.opentelemetry.context.Context;
28+
import io.opentelemetry.sdk.trace.ReadWriteSpan;
29+
import io.opentelemetry.sdk.trace.ReadableSpan;
30+
import io.opentelemetry.sdk.trace.SpanProcessor;
31+
import java.util.List;
32+
import java.util.stream.Collectors;
33+
34+
public class InheritedAttributesSpanProcessor implements SpanProcessor {
35+
36+
private final List<AttributeKey<?>> inheritAttributeKeys;
37+
38+
public InheritedAttributesSpanProcessor(
39+
List<Configuration.InheritedAttribute> inheritedAttributes) {
40+
this.inheritAttributeKeys =
41+
inheritedAttributes.stream()
42+
.map(Configuration.InheritedAttribute::getAttributeKey)
43+
.collect(Collectors.toList());
44+
}
45+
46+
@Override
47+
@SuppressWarnings("unchecked")
48+
public void onStart(Context parentContext, ReadWriteSpan span) {
49+
Span parentSpan = Span.fromContextOrNull(parentContext);
50+
if (parentSpan == null) {
51+
return;
52+
}
53+
if (!(parentSpan instanceof ReadableSpan)) {
54+
return;
55+
}
56+
ReadableSpan parentReadableSpan = (ReadableSpan) parentSpan;
57+
58+
for (AttributeKey<?> inheritAttributeKey : inheritAttributeKeys) {
59+
Object value = TempGetAttribute.getAttribute(parentReadableSpan, inheritAttributeKey);
60+
if (value != null) {
61+
span.setAttribute((AttributeKey<Object>) inheritAttributeKey, value);
62+
}
63+
}
64+
}
65+
66+
@Override
67+
public boolean isStartRequired() {
68+
return true;
69+
}
70+
71+
@Override
72+
public void onEnd(ReadableSpan span) {}
73+
74+
@Override
75+
public boolean isEndRequired() {
76+
return false;
77+
}
78+
}

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/OpenTelemetryConfigurer.java

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,20 +107,23 @@ public void configure(SdkTracerProviderBuilder tracerProvider) {
107107
}
108108
}
109109

110-
// using BatchSpanProcessor in order to get off of the application thread as soon as possible
111-
// using batch size 1 because need to convert to SpanData as soon as possible to grab data for
112-
// live metrics
113-
// real batching is done at a lower level
114-
batchSpanProcessor = BatchSpanProcessor.builder(currExporter).setMaxExportBatchSize(1).build();
115-
tracerProvider.addSpanProcessor(batchSpanProcessor);
116110
// operation name span processor is only applied on span start, so doesn't need to be chained
117-
// with above span processors
111+
// with the batch span processor
118112
tracerProvider.addSpanProcessor(new AiOperationNameSpanProcessor());
119-
// legacy span processor is only applied on span start, so doesn't need to be chained with above
120-
// span processors
113+
// inherited attributes span processor is only applied on span start, so doesn't need to be
114+
// chained with the batch span processor
115+
tracerProvider.addSpanProcessor(
116+
new InheritedAttributesSpanProcessor(config.preview.inheritedAttributes));
117+
// legacy span processor is only applied on span start, so doesn't need to be chained with the
118+
// batch span processor
121119
// it is used to pass legacy attributes from the context (extracted by the AiLegacyPropagator)
122120
// to the span attributes (since there is no way to update attributes on span directly from
123121
// propagator)
124122
tracerProvider.addSpanProcessor(new AiLegacyHeaderSpanProcessor());
123+
// using BatchSpanProcessor in order to get off of the application thread as soon as possible
124+
// using batch size 1 because need to convert to SpanData as soon as possible to grab data for
125+
// live metrics. the real batching is done at a lower level
126+
batchSpanProcessor = BatchSpanProcessor.builder(currExporter).setMaxExportBatchSize(1).build();
127+
tracerProvider.addSpanProcessor(batchSpanProcessor);
125128
}
126129
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*
2+
* ApplicationInsights-Java
3+
* Copyright (c) Microsoft Corporation
4+
* All rights reserved.
5+
*
6+
* MIT License
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
8+
* software and associated documentation files (the ""Software""), to deal in the Software
9+
* without restriction, including without limitation the rights to use, copy, modify, merge,
10+
* publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11+
* persons to whom the Software is furnished to do so, subject to the following conditions:
12+
* The above copyright notice and this permission notice shall be included in all copies or
13+
* substantial portions of the Software.
14+
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15+
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16+
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17+
* FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18+
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19+
* DEALINGS IN THE SOFTWARE.
20+
*/
21+
22+
package com.microsoft.applicationinsights.agent.internal.init;
23+
24+
import static io.opentelemetry.api.trace.SpanKind.INTERNAL;
25+
import static io.opentelemetry.sdk.testing.assertj.TracesAssert.assertThat;
26+
import static org.assertj.core.api.Assertions.entry;
27+
import static org.awaitility.Awaitility.await;
28+
29+
import com.microsoft.applicationinsights.agent.internal.configuration.Configuration;
30+
import io.opentelemetry.api.common.AttributeKey;
31+
import io.opentelemetry.api.trace.Span;
32+
import io.opentelemetry.api.trace.Tracer;
33+
import io.opentelemetry.context.Context;
34+
import io.opentelemetry.sdk.OpenTelemetrySdk;
35+
import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions;
36+
import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter;
37+
import io.opentelemetry.sdk.trace.SdkTracerProvider;
38+
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
39+
import java.util.Collections;
40+
import java.util.List;
41+
import org.junit.jupiter.api.AfterEach;
42+
import org.junit.jupiter.api.Test;
43+
44+
public class InheritedAttributesSpanProcessorTest {
45+
46+
private final InMemorySpanExporter exporter = InMemorySpanExporter.create();
47+
48+
private final AttributeKey<String> oneStringKey = AttributeKey.stringKey("one");
49+
private final AttributeKey<Long> oneLongKey = AttributeKey.longKey("one");
50+
51+
@AfterEach
52+
public void afterEach() {
53+
exporter.reset();
54+
}
55+
56+
@Test
57+
public void shouldNotInheritAttribute() {
58+
Tracer tracer = newTracer(Collections.emptyList());
59+
Span span =
60+
tracer.spanBuilder("parent").setNoParent().setAttribute(oneStringKey, "1").startSpan();
61+
Context context = Context.root().with(span);
62+
try {
63+
tracer.spanBuilder("child").setParent(context).startSpan().end();
64+
} finally {
65+
span.end();
66+
}
67+
68+
await().until(() -> exporter.getFinishedSpanItems().size() == 2);
69+
70+
assertThat(Collections.singleton(exporter.getFinishedSpanItems()))
71+
.hasTracesSatisfyingExactly(
72+
trace ->
73+
trace.hasSpansSatisfyingExactly(
74+
childSpan ->
75+
childSpan.hasName("child").hasKind(INTERNAL).hasTotalAttributeCount(0),
76+
parentSpan ->
77+
parentSpan
78+
.hasName("parent")
79+
.hasKind(INTERNAL)
80+
.hasAttributesSatisfying(
81+
attributes ->
82+
OpenTelemetryAssertions.assertThat(attributes)
83+
.containsOnly(entry(oneStringKey, "1")))));
84+
}
85+
86+
@Test
87+
public void shouldInheritAttribute() {
88+
Configuration.InheritedAttribute inheritedAttribute = new Configuration.InheritedAttribute();
89+
inheritedAttribute.key = "one";
90+
inheritedAttribute.type = Configuration.SpanAttributeType.STRING;
91+
92+
Tracer tracer = newTracer(Collections.singletonList(inheritedAttribute));
93+
Span span =
94+
tracer.spanBuilder("parent").setNoParent().setAttribute(oneStringKey, "1").startSpan();
95+
Context context = Context.root().with(span);
96+
try {
97+
tracer.spanBuilder("child").setParent(context).startSpan().end();
98+
} finally {
99+
span.end();
100+
}
101+
102+
await().until(() -> exporter.getFinishedSpanItems().size() == 2);
103+
104+
assertThat(Collections.singleton(exporter.getFinishedSpanItems()))
105+
.hasTracesSatisfyingExactly(
106+
trace ->
107+
trace.hasSpansSatisfyingExactly(
108+
childSpan ->
109+
childSpan
110+
.hasName("child")
111+
.hasKind(INTERNAL)
112+
.hasAttributesSatisfying(
113+
attributes ->
114+
OpenTelemetryAssertions.assertThat(attributes)
115+
.containsOnly(entry(oneStringKey, "1"))),
116+
parentSpan ->
117+
parentSpan
118+
.hasName("parent")
119+
.hasKind(INTERNAL)
120+
.hasAttributesSatisfying(
121+
attributes ->
122+
OpenTelemetryAssertions.assertThat(attributes)
123+
.containsOnly(entry(oneStringKey, "1")))));
124+
}
125+
126+
@Test
127+
public void shouldNotInheritAttributeWithSameNameButDifferentType() {
128+
Configuration.InheritedAttribute inheritedAttribute = new Configuration.InheritedAttribute();
129+
inheritedAttribute.key = "one";
130+
inheritedAttribute.type = Configuration.SpanAttributeType.STRING;
131+
132+
Tracer tracer = newTracer(Collections.singletonList(inheritedAttribute));
133+
Span span = tracer.spanBuilder("parent").setNoParent().setAttribute(oneLongKey, 1L).startSpan();
134+
Context context = Context.root().with(span);
135+
try {
136+
tracer.spanBuilder("child").setParent(context).startSpan().end();
137+
} finally {
138+
span.end();
139+
}
140+
141+
await().until(() -> exporter.getFinishedSpanItems().size() == 2);
142+
143+
assertThat(Collections.singleton(exporter.getFinishedSpanItems()))
144+
.hasTracesSatisfyingExactly(
145+
trace ->
146+
trace.hasSpansSatisfyingExactly(
147+
childSpan ->
148+
childSpan.hasName("child").hasKind(INTERNAL).hasTotalAttributeCount(0),
149+
parentSpan ->
150+
parentSpan
151+
.hasName("parent")
152+
.hasKind(INTERNAL)
153+
.hasAttributesSatisfying(
154+
attributes ->
155+
OpenTelemetryAssertions.assertThat(attributes)
156+
.containsOnly(entry(oneLongKey, 1L)))));
157+
}
158+
159+
private Tracer newTracer(List<Configuration.InheritedAttribute> inheritedAttributes) {
160+
OpenTelemetrySdk sdk =
161+
OpenTelemetrySdk.builder()
162+
.setTracerProvider(
163+
SdkTracerProvider.builder()
164+
.addSpanProcessor(new InheritedAttributesSpanProcessor(inheritedAttributes))
165+
.addSpanProcessor(SimpleSpanProcessor.create(exporter))
166+
.build())
167+
.build();
168+
return sdk.getTracer("test");
169+
}
170+
}

dependencyManagement/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ val DEPENDENCIES = listOf(
102102
"com.azure:azure-storage-blob:12.13.0",
103103
"com.github.oshi:oshi-core:5.8.0",
104104
"org.assertj:assertj-core:3.19.0",
105+
"org.awaitility:awaitility:4.1.0",
105106
"io.github.hakky54:logcaptor:2.5.0",
106107
"com.microsoft.jfr:jfr-streaming:1.2.0",
107108
"org.checkerframework:checker-qual:3.14.0"

settings.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ include ':test:smoke:testApps:CustomDimensions'
8888
include ':test:smoke:testApps:gRPC'
8989
include ':test:smoke:testApps:HeartBeat'
9090
include ':test:smoke:testApps:HttpClients'
91+
include ':test:smoke:testApps:InheritedAttributes'
9192
include ':test:smoke:testApps:Jdbc'
9293
include ':test:smoke:testApps:Jedis'
9394
include ':test:smoke:testApps:JettyNativeHandler'

0 commit comments

Comments
 (0)