Skip to content

Commit d22dd8c

Browse files
author
Mateusz Rzeszutek
authored
Introduce new incubating InstrumenterBuilder methods (#8392)
1 parent dbf7149 commit d22dd8c

File tree

6 files changed

+219
-46
lines changed

6 files changed

+219
-46
lines changed

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import io.opentelemetry.api.trace.SpanKind;
1212
import io.opentelemetry.api.trace.Tracer;
1313
import io.opentelemetry.context.Context;
14+
import io.opentelemetry.instrumentation.api.internal.InstrumenterAccess;
15+
import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil;
1416
import io.opentelemetry.instrumentation.api.internal.SupportabilityMetrics;
1517
import java.time.Instant;
1618
import java.util.ArrayList;
@@ -250,4 +252,22 @@ private static long getNanos(@Nullable Instant time) {
250252
}
251253
return TimeUnit.SECONDS.toNanos(time.getEpochSecond()) + time.getNano();
252254
}
255+
256+
static {
257+
InstrumenterUtil.setInstrumenterAccess(
258+
new InstrumenterAccess() {
259+
@Override
260+
public <RQ, RS> Context startAndEnd(
261+
Instrumenter<RQ, RS> instrumenter,
262+
Context parentContext,
263+
RQ request,
264+
@Nullable RS response,
265+
@Nullable Throwable error,
266+
Instant startTime,
267+
Instant endTime) {
268+
return instrumenter.startAndEnd(
269+
parentContext, request, response, error, startTime, endTime);
270+
}
271+
});
272+
}
253273
}

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

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import io.opentelemetry.context.propagation.TextMapSetter;
2121
import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil;
2222
import io.opentelemetry.instrumentation.api.internal.EmbeddedInstrumentationProperties;
23+
import io.opentelemetry.instrumentation.api.internal.InstrumenterBuilderAccess;
24+
import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil;
2325
import io.opentelemetry.instrumentation.api.internal.SpanKey;
2426
import io.opentelemetry.instrumentation.api.internal.SpanKeyProvider;
2527
import java.util.ArrayList;
@@ -187,7 +189,7 @@ public InstrumenterBuilder<REQUEST, RESPONSE> setEnabled(boolean enabled) {
187189

188190
/**
189191
* Returns a new {@link Instrumenter} which will create {@linkplain SpanKind#CLIENT client} spans
190-
* and inject context into requests.
192+
* and inject context into requests with the passed {@link TextMapSetter}.
191193
*/
192194
public Instrumenter<REQUEST, RESPONSE> buildClientInstrumenter(TextMapSetter<REQUEST> setter) {
193195
return buildInstrumenter(
@@ -197,7 +199,7 @@ public Instrumenter<REQUEST, RESPONSE> buildClientInstrumenter(TextMapSetter<REQ
197199

198200
/**
199201
* Returns a new {@link Instrumenter} which will create {@linkplain SpanKind#SERVER server} spans
200-
* and extract context from requests.
202+
* and extract context from requests with the passed {@link TextMapGetter}.
201203
*/
202204
public Instrumenter<REQUEST, RESPONSE> buildServerInstrumenter(TextMapGetter<REQUEST> getter) {
203205
return buildInstrumenter(
@@ -207,7 +209,7 @@ public Instrumenter<REQUEST, RESPONSE> buildServerInstrumenter(TextMapGetter<REQ
207209

208210
/**
209211
* Returns a new {@link Instrumenter} which will create {@linkplain SpanKind#PRODUCER producer}
210-
* spans and inject context into requests.
212+
* spans and inject context into requests with the passed {@link TextMapSetter}.
211213
*/
212214
public Instrumenter<REQUEST, RESPONSE> buildProducerInstrumenter(TextMapSetter<REQUEST> setter) {
213215
return buildInstrumenter(
@@ -217,14 +219,40 @@ public Instrumenter<REQUEST, RESPONSE> buildProducerInstrumenter(TextMapSetter<R
217219

218220
/**
219221
* Returns a new {@link Instrumenter} which will create {@linkplain SpanKind#CONSUMER consumer}
220-
* spans and extract context from requests.
222+
* spans and extract context from requests with the passed {@link TextMapGetter}.
221223
*/
222224
public Instrumenter<REQUEST, RESPONSE> buildConsumerInstrumenter(TextMapGetter<REQUEST> getter) {
223225
return buildInstrumenter(
224226
InstrumenterConstructor.propagatingFromUpstream(requireNonNull(getter, "getter")),
225227
SpanKindExtractor.alwaysConsumer());
226228
}
227229

230+
/**
231+
* Returns a new {@link Instrumenter} which will create spans with kind determined by the passed
232+
* {@link SpanKindExtractor} and extract context from requests with the passed {@link
233+
* TextMapGetter}.
234+
*/
235+
// TODO: candidate for public API
236+
Instrumenter<REQUEST, RESPONSE> buildUpstreamInstrumenter(
237+
TextMapGetter<REQUEST> getter, SpanKindExtractor<REQUEST> spanKindExtractor) {
238+
return buildInstrumenter(
239+
InstrumenterConstructor.propagatingFromUpstream(requireNonNull(getter, "getter")),
240+
spanKindExtractor);
241+
}
242+
243+
/**
244+
* Returns a new {@link Instrumenter} which will create spans with kind determined by the passed
245+
* {@link SpanKindExtractor} and inject context into requests with the passed {@link
246+
* TextMapSetter}.
247+
*/
248+
// TODO: candidate for public API
249+
Instrumenter<REQUEST, RESPONSE> buildDownstreamInstrumenter(
250+
TextMapSetter<REQUEST> setter, SpanKindExtractor<REQUEST> spanKindExtractor) {
251+
return buildInstrumenter(
252+
InstrumenterConstructor.propagatingToDownstream(requireNonNull(setter, "setter")),
253+
spanKindExtractor);
254+
}
255+
228256
/**
229257
* Returns a new {@link Instrumenter} which will create {@linkplain SpanKind#INTERNAL internal}
230258
* spans and do no context propagation.
@@ -321,4 +349,25 @@ static <RQ, RS> InstrumenterConstructor<RQ, RS> propagatingFromUpstream(
321349
return builder -> new PropagatingFromUpstreamInstrumenter<>(builder, getter);
322350
}
323351
}
352+
353+
static {
354+
InstrumenterUtil.setInstrumenterBuilderAccess(
355+
new InstrumenterBuilderAccess() {
356+
@Override
357+
public <RQ, RS> Instrumenter<RQ, RS> buildUpstreamInstrumenter(
358+
InstrumenterBuilder<RQ, RS> builder,
359+
TextMapGetter<RQ> getter,
360+
SpanKindExtractor<RQ> spanKindExtractor) {
361+
return builder.buildUpstreamInstrumenter(getter, spanKindExtractor);
362+
}
363+
364+
@Override
365+
public <RQ, RS> Instrumenter<RQ, RS> buildDownstreamInstrumenter(
366+
InstrumenterBuilder<RQ, RS> builder,
367+
TextMapSetter<RQ> setter,
368+
SpanKindExtractor<RQ> spanKindExtractor) {
369+
return builder.buildDownstreamInstrumenter(setter, spanKindExtractor);
370+
}
371+
});
372+
}
324373
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.api.internal;
7+
8+
import io.opentelemetry.context.Context;
9+
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
10+
import java.time.Instant;
11+
import javax.annotation.Nullable;
12+
13+
/**
14+
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
15+
* any time.
16+
*/
17+
public interface InstrumenterAccess {
18+
19+
<REQUEST, RESPONSE> Context startAndEnd(
20+
Instrumenter<REQUEST, RESPONSE> instrumenter,
21+
Context parentContext,
22+
REQUEST request,
23+
@Nullable RESPONSE response,
24+
@Nullable Throwable error,
25+
Instant startTime,
26+
Instant endTime);
27+
}
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.instrumentation.api.internal;
7+
8+
import io.opentelemetry.context.propagation.TextMapGetter;
9+
import io.opentelemetry.context.propagation.TextMapSetter;
10+
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
11+
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
12+
import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;
13+
14+
/**
15+
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
16+
* any time.
17+
*/
18+
public interface InstrumenterBuilderAccess {
19+
20+
<REQUEST, RESPONSE> Instrumenter<REQUEST, RESPONSE> buildUpstreamInstrumenter(
21+
InstrumenterBuilder<REQUEST, RESPONSE> builder,
22+
TextMapGetter<REQUEST> getter,
23+
SpanKindExtractor<REQUEST> spanKindExtractor);
24+
25+
<REQUEST, RESPONSE> Instrumenter<REQUEST, RESPONSE> buildDownstreamInstrumenter(
26+
InstrumenterBuilder<REQUEST, RESPONSE> builder,
27+
TextMapSetter<REQUEST> setter,
28+
SpanKindExtractor<REQUEST> spanKindExtractor);
29+
}

instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/InstrumenterUtil.java

Lines changed: 31 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
package io.opentelemetry.instrumentation.api.internal;
77

88
import io.opentelemetry.context.Context;
9+
import io.opentelemetry.context.propagation.TextMapGetter;
10+
import io.opentelemetry.context.propagation.TextMapSetter;
911
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
10-
import java.lang.reflect.InvocationTargetException;
11-
import java.lang.reflect.Method;
12+
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
13+
import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;
1214
import java.time.Instant;
13-
import java.util.logging.Level;
14-
import java.util.logging.Logger;
1515
import javax.annotation.Nullable;
1616

1717
/**
@@ -20,28 +20,16 @@
2020
*/
2121
public final class InstrumenterUtil {
2222

23-
private static final Logger logger = Logger.getLogger(InstrumenterUtil.class.getName());
23+
private static InstrumenterAccess instrumenterAccess;
24+
private static InstrumenterBuilderAccess instrumenterBuilderAccess;
2425

25-
private static final Method startAndEndMethod;
26+
public static void setInstrumenterAccess(InstrumenterAccess instrumenterAccess) {
27+
InstrumenterUtil.instrumenterAccess = instrumenterAccess;
28+
}
2629

27-
static {
28-
Method method = null;
29-
try {
30-
method =
31-
Instrumenter.class.getDeclaredMethod(
32-
"startAndEnd",
33-
Context.class,
34-
Object.class,
35-
Object.class,
36-
Throwable.class,
37-
Instant.class,
38-
Instant.class);
39-
method.setAccessible(true);
40-
} catch (NoSuchMethodException e) {
41-
logger.log(
42-
Level.WARNING, "Could not get Instrumenter#startAndEnd() method with reflection", e);
43-
}
44-
startAndEndMethod = method;
30+
public static void setInstrumenterBuilderAccess(
31+
InstrumenterBuilderAccess instrumenterBuilderAccess) {
32+
InstrumenterUtil.instrumenterBuilderAccess = instrumenterBuilderAccess;
4533
}
4634

4735
public static <REQUEST, RESPONSE> Context startAndEnd(
@@ -52,19 +40,26 @@ public static <REQUEST, RESPONSE> Context startAndEnd(
5240
@Nullable Throwable error,
5341
Instant startTime,
5442
Instant endTime) {
43+
// instrumenterAccess is guaranteed to be non-null here
44+
return instrumenterAccess.startAndEnd(
45+
instrumenter, parentContext, request, response, error, startTime, endTime);
46+
}
47+
48+
public static <REQUEST, RESPONSE> Instrumenter<REQUEST, RESPONSE> buildUpstreamInstrumenter(
49+
InstrumenterBuilder<REQUEST, RESPONSE> builder,
50+
TextMapGetter<REQUEST> getter,
51+
SpanKindExtractor<REQUEST> spanKindExtractor) {
52+
// instrumenterBuilderAccess is guaranteed to be non-null here
53+
return instrumenterBuilderAccess.buildUpstreamInstrumenter(builder, getter, spanKindExtractor);
54+
}
5555

56-
if (startAndEndMethod == null) {
57-
// already logged a warning when this class initialized
58-
return parentContext;
59-
}
60-
try {
61-
return (Context)
62-
startAndEndMethod.invoke(
63-
instrumenter, parentContext, request, response, error, startTime, endTime);
64-
} catch (InvocationTargetException | IllegalAccessException e) {
65-
logger.log(Level.WARNING, "Error occurred when calling Instrumenter#startAndEnd()", e);
66-
return parentContext;
67-
}
56+
public static <REQUEST, RESPONSE> Instrumenter<REQUEST, RESPONSE> buildDownstreamInstrumenter(
57+
InstrumenterBuilder<REQUEST, RESPONSE> builder,
58+
TextMapSetter<REQUEST> setter,
59+
SpanKindExtractor<REQUEST> spanKindExtractor) {
60+
// instrumenterBuilderAccess is guaranteed to be non-null here
61+
return instrumenterBuilderAccess.buildDownstreamInstrumenter(
62+
builder, setter, spanKindExtractor);
6863
}
6964

7065
private InstrumenterUtil() {}

instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
99
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
10+
import static java.util.Collections.emptyMap;
1011
import static org.assertj.core.api.Assertions.entry;
1112
import static org.mockito.Mockito.when;
1213

@@ -361,6 +362,58 @@ void client_parent() {
361362
.hasParentSpanId("090a0b0c0d0e0f00")));
362363
}
363364

365+
@Test
366+
void upstream_customSpanKind() {
367+
Instrumenter<Map<String, String>, Map<String, String>> instrumenter =
368+
Instrumenter.<Map<String, String>, Map<String, String>>builder(
369+
otelTesting.getOpenTelemetry(), "test", unused -> "span")
370+
.buildUpstreamInstrumenter(new MapGetter(), SpanKindExtractor.alwaysInternal());
371+
372+
Map<String, String> request = new HashMap<>();
373+
request.put("traceparent", "00-ff01020304050600ff0a0b0c0d0e0f00-090a0b0c0d0e0f00-01");
374+
Context context = instrumenter.start(Context.root(), request);
375+
376+
SpanContext spanContext = Span.fromContext(context).getSpanContext();
377+
assertThat(spanContext.isValid()).isTrue();
378+
379+
instrumenter.end(context, request, emptyMap(), null);
380+
381+
otelTesting
382+
.assertTraces()
383+
.hasTracesSatisfyingExactly(
384+
trace ->
385+
trace.hasSpansSatisfyingExactly(
386+
span ->
387+
span.hasName("span")
388+
.hasKind(SpanKind.INTERNAL)
389+
.hasTraceId("ff01020304050600ff0a0b0c0d0e0f00")
390+
.hasParentSpanId("090a0b0c0d0e0f00")));
391+
}
392+
393+
@Test
394+
void downstream_customSpanKind() {
395+
Instrumenter<Map<String, String>, Map<String, String>> instrumenter =
396+
Instrumenter.<Map<String, String>, Map<String, String>>builder(
397+
otelTesting.getOpenTelemetry(), "test", unused -> "span")
398+
.buildDownstreamInstrumenter(Map::put, SpanKindExtractor.alwaysInternal());
399+
400+
Map<String, String> request = new HashMap<>();
401+
Context context = instrumenter.start(Context.root(), request);
402+
403+
SpanContext spanContext = Span.fromContext(context).getSpanContext();
404+
assertThat(spanContext.isValid()).isTrue();
405+
assertThat(request).containsKey("traceparent");
406+
407+
instrumenter.end(context, request, emptyMap(), null);
408+
409+
otelTesting
410+
.assertTraces()
411+
.hasTracesSatisfyingExactly(
412+
trace ->
413+
trace.hasSpansSatisfyingExactly(
414+
span -> span.hasName("span").hasKind(SpanKind.INTERNAL)));
415+
}
416+
364417
@Test
365418
void operationListeners() {
366419
AtomicReference<Boolean> startContext = new AtomicReference<>();
@@ -486,10 +539,10 @@ void instrumentationVersion_default() {
486539
Instrumenter<Map<String, String>, Map<String, String>> instrumenter =
487540
builder.buildInstrumenter();
488541

489-
Context context = instrumenter.start(Context.root(), Collections.emptyMap());
542+
Context context = instrumenter.start(Context.root(), emptyMap());
490543
assertThat(Span.fromContext(context)).isNotNull();
491544

492-
instrumenter.end(context, Collections.emptyMap(), Collections.emptyMap(), null);
545+
instrumenter.end(context, emptyMap(), emptyMap(), null);
493546

494547
// see the test-instrumentation.properties file
495548
InstrumentationScopeInfo expectedLibraryInfo =
@@ -511,10 +564,10 @@ void instrumentationVersion_custom() {
511564
.setInstrumentationVersion("1.0")
512565
.buildInstrumenter();
513566

514-
Context context = instrumenter.start(Context.root(), Collections.emptyMap());
567+
Context context = instrumenter.start(Context.root(), emptyMap());
515568
assertThat(Span.fromContext(context)).isNotNull();
516569

517-
instrumenter.end(context, Collections.emptyMap(), Collections.emptyMap(), null);
570+
instrumenter.end(context, emptyMap(), emptyMap(), null);
518571

519572
otelTesting
520573
.assertTraces()
@@ -537,10 +590,10 @@ void schemaUrl() {
537590
.setSchemaUrl("https://opentelemetry.io/schemas/1.0.0")
538591
.buildInstrumenter();
539592

540-
Context context = instrumenter.start(Context.root(), Collections.emptyMap());
593+
Context context = instrumenter.start(Context.root(), emptyMap());
541594
assertThat(Span.fromContext(context)).isNotNull();
542595

543-
instrumenter.end(context, Collections.emptyMap(), Collections.emptyMap(), null);
596+
instrumenter.end(context, emptyMap(), emptyMap(), null);
544597

545598
InstrumentationScopeInfo expectedLibraryInfo =
546599
InstrumentationScopeInfo.builder("test")

0 commit comments

Comments
 (0)