Instrumenter instrumentation version and schema url (#5752)

* Instrumenter instrumentation version and schema url

* nullable instrumentation version

* Apply suggestions from code review

Co-authored-by: Fabrizio Ferri-Benedetti <fferribenedetti@splunk.com>

* reformat

* code review comments

* instrumentation properties files

* Apply suggestions from code review

Co-authored-by: Fabrizio Ferri-Benedetti <fferribenedetti@splunk.com>

* code review comments

* Apply suggestions from code review

Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>

Co-authored-by: Fabrizio Ferri-Benedetti <fferribenedetti@splunk.com>
Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
This commit is contained in:
Mateusz Rzeszutek 2022-04-11 10:29:58 +02:00 committed by GitHub
parent ad2243fcf4
commit 2ce1162eac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 136 additions and 25 deletions

View File

@ -116,6 +116,31 @@ The `builder()` method accepts three arguments:
An `Instrumenter` can be built from several smaller components. The following subsections describe
all interfaces that can be used to customize an `Instrumenter`.
### Set the instrumentation version and OpenTelemetry schema URL
By setting the instrumentation library version, you let users identify which version of your
instrumentation produced the telemetry. Make sure you always provide the version to
the `Instrumenter`. You can do this in two ways:
* By calling the `setInstrumentationVersion()` method on the `InstrumenterBuilder`.
* By making sure that the JAR file with your instrumentation library contains a properties file in
the `META-INF/io/opentelemetry/instrumentation/` directory. You must name the file
`${instrumentationName}.properties`, where `${instrumentationName}` is the name of the
instrumentation library passed to the `Instrumenter#builder()` method. The file must contain a
single property, `version`. For example:
```properties
# META-INF/io/opentelemetry/instrumentation/my-instrumentation.properties
version=1.2.3
```
The `Instrumenter` automatically detects the properties file and determines the instrumentation
version based on its name.
If the `Instrumenter` adheres to a specific OpenTelemetry schema, you can set the schema URL using
the `setSchemaUrl()` method on the `InstrumenterBuilder`. To learn more about the OpenTelemetry
schemas [see the Overview](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/schemas/overview.md).
### Name the spans using the `SpanNameExtractor`
A `SpanNameExtractor` is a simple functional interface that accepts the `REQUEST` type and returns

View File

@ -13,7 +13,6 @@ import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.internal.EmbeddedInstrumentationProperties;
import io.opentelemetry.instrumentation.api.internal.SupportabilityMetrics;
import java.time.Instant;
import java.util.ArrayList;
@ -59,11 +58,7 @@ public class Instrumenter<REQUEST, RESPONSE> {
OpenTelemetry openTelemetry,
String instrumentationName,
SpanNameExtractor<? super REQUEST> spanNameExtractor) {
return new InstrumenterBuilder<>(
openTelemetry,
instrumentationName,
EmbeddedInstrumentationProperties.findVersion(instrumentationName),
spanNameExtractor);
return new InstrumenterBuilder<>(openTelemetry, instrumentationName, spanNameExtractor);
}
/**
@ -82,15 +77,19 @@ public class Instrumenter<REQUEST, RESPONSE> {
* io.opentelemetry.apache-httpclient-4.0}. This way, if there are different instrumentations for
* different library versions it's easy to find out which instrumentations produced the telemetry
* data.
*
* @deprecated Use the {@link InstrumenterBuilder#setInstrumentationVersion(String)} method
* instead.
*/
// TODO: add a setInstrumentationVersion method to the builder instead
@Deprecated
public static <REQUEST, RESPONSE> InstrumenterBuilder<REQUEST, RESPONSE> builder(
OpenTelemetry openTelemetry,
String instrumentationName,
String instrumentationVersion,
SpanNameExtractor<? super REQUEST> spanNameExtractor) {
return new InstrumenterBuilder<>(
openTelemetry, instrumentationName, instrumentationVersion, spanNameExtractor);
return Instrumenter.<REQUEST, RESPONSE>builder(
openTelemetry, instrumentationName, spanNameExtractor)
.setInstrumentationVersion(instrumentationVersion);
}
private static final SupportabilityMetrics supportability = SupportabilityMetrics.instance();
@ -112,19 +111,18 @@ public class Instrumenter<REQUEST, RESPONSE> {
Instrumenter(InstrumenterBuilder<REQUEST, RESPONSE> builder) {
this.instrumentationName = builder.instrumentationName;
this.tracer =
builder.openTelemetry.getTracer(instrumentationName, builder.instrumentationVersion);
this.tracer = builder.buildTracer();
this.spanNameExtractor = builder.spanNameExtractor;
this.spanKindExtractor = builder.spanKindExtractor;
this.spanStatusExtractor = builder.spanStatusExtractor;
this.spanLinksExtractors = new ArrayList<>(builder.spanLinksExtractors);
this.attributesExtractors = new ArrayList<>(builder.attributesExtractors);
this.contextCustomizers = new ArrayList<>(builder.contextCustomizers);
this.requestListeners = new ArrayList<>(builder.requestListeners);
this.requestListeners = builder.buildRequestListeners();
this.errorCauseExtractor = builder.errorCauseExtractor;
this.timeExtractor = builder.timeExtractor;
this.enabled = builder.enabled;
this.spanSuppressionStrategy = builder.getSpanSuppressionStrategy();
this.spanSuppressionStrategy = builder.buildSpanSuppressionStrategy();
}
/**

View File

@ -9,12 +9,16 @@ import static java.util.Objects.requireNonNull;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.metrics.MeterBuilder;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.api.trace.TracerBuilder;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.TextMapGetter;
import io.opentelemetry.context.propagation.TextMapSetter;
import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.instrumentation.api.internal.EmbeddedInstrumentationProperties;
import io.opentelemetry.instrumentation.api.internal.SpanKey;
import io.opentelemetry.instrumentation.api.internal.SpanKeyProvider;
import java.util.ArrayList;
@ -38,17 +42,18 @@ public final class InstrumenterBuilder<REQUEST, RESPONSE> {
.getBoolean("otel.instrumentation.experimental.outgoing-span-suppression-by-type", false);
final OpenTelemetry openTelemetry;
final Meter meter;
final String instrumentationName;
final String instrumentationVersion;
final SpanNameExtractor<? super REQUEST> spanNameExtractor;
final List<SpanLinksExtractor<? super REQUEST>> spanLinksExtractors = new ArrayList<>();
final List<AttributesExtractor<? super REQUEST, ? super RESPONSE>> attributesExtractors =
new ArrayList<>();
final List<ContextCustomizer<? super REQUEST>> contextCustomizers = new ArrayList<>();
final List<RequestListener> requestListeners = new ArrayList<>();
private final List<RequestListener> requestListeners = new ArrayList<>();
private final List<RequestMetrics> requestMetrics = new ArrayList<>();
@Nullable private String instrumentationVersion;
@Nullable private String schemaUrl = null;
SpanKindExtractor<? super REQUEST> spanKindExtractor = SpanKindExtractor.alwaysInternal();
SpanStatusExtractor<? super REQUEST, ? super RESPONSE> spanStatusExtractor =
SpanStatusExtractor.getDefault();
@ -61,13 +66,34 @@ public final class InstrumenterBuilder<REQUEST, RESPONSE> {
InstrumenterBuilder(
OpenTelemetry openTelemetry,
String instrumentationName,
String instrumentationVersion,
SpanNameExtractor<? super REQUEST> spanNameExtractor) {
this.openTelemetry = openTelemetry;
this.meter = openTelemetry.getMeterProvider().get(instrumentationName);
this.instrumentationName = instrumentationName;
this.instrumentationVersion = instrumentationVersion;
this.spanNameExtractor = spanNameExtractor;
this.instrumentationVersion =
EmbeddedInstrumentationProperties.findVersion(instrumentationName);
}
/**
* Sets the instrumentation version that'll be associated with all telemetry produced by this
* {@link Instrumenter}.
*
* @param instrumentationVersion is the version of the instrumentation library, not the version of
* the instrument*ed* library.
*/
public InstrumenterBuilder<REQUEST, RESPONSE> setInstrumentationVersion(
String instrumentationVersion) {
this.instrumentationVersion = instrumentationVersion;
return this;
}
/**
* Sets the OpenTelemetry schema URL that'll be associated with all telemetry produced by this
* {@link Instrumenter}.
*/
public InstrumenterBuilder<REQUEST, RESPONSE> setSchemaUrl(String schemaUrl) {
this.schemaUrl = schemaUrl;
return this;
}
/**
@ -127,7 +153,7 @@ public final class InstrumenterBuilder<REQUEST, RESPONSE> {
/** Adds a {@link RequestMetrics} whose metrics will be recorded for request start and end. */
public InstrumenterBuilder<REQUEST, RESPONSE> addRequestMetrics(RequestMetrics factory) {
requestListeners.add(factory.create(meter));
requestMetrics.add(factory);
return this;
}
@ -274,7 +300,44 @@ public final class InstrumenterBuilder<REQUEST, RESPONSE> {
return constructor.create(this);
}
SpanSuppressionStrategy getSpanSuppressionStrategy() {
Tracer buildTracer() {
TracerBuilder tracerBuilder =
openTelemetry.getTracerProvider().tracerBuilder(instrumentationName);
if (instrumentationVersion != null) {
tracerBuilder.setInstrumentationVersion(instrumentationVersion);
}
if (schemaUrl != null) {
tracerBuilder.setSchemaUrl(schemaUrl);
}
return tracerBuilder.build();
}
List<RequestListener> buildRequestListeners() {
// just copy the listeners list if there are no metrics registered
if (requestMetrics.isEmpty()) {
return new ArrayList<>(requestListeners);
}
List<RequestListener> listeners =
new ArrayList<>(requestListeners.size() + requestMetrics.size());
listeners.addAll(requestListeners);
MeterBuilder meterBuilder = openTelemetry.getMeterProvider().meterBuilder(instrumentationName);
if (instrumentationVersion != null) {
meterBuilder.setInstrumentationVersion(instrumentationVersion);
}
if (schemaUrl != null) {
meterBuilder.setSchemaUrl(schemaUrl);
}
Meter meter = meterBuilder.build();
for (RequestMetrics factory : requestMetrics) {
listeners.add(factory.create(meter));
}
return listeners;
}
SpanSuppressionStrategy buildSpanSuppressionStrategy() {
Set<SpanKey> spanKeys = getSpanKeysFromAttributesExtractors();
if (enableSpanSuppressionByType) {
return SpanSuppressionStrategy.from(spanKeys);

View File

@ -749,10 +749,11 @@ class InstrumenterTest {
@Test
void instrumentationVersion_custom() {
InstrumenterBuilder<Map<String, String>, Map<String, String>> builder =
Instrumenter.builder(otelTesting.getOpenTelemetry(), "test", "1.0", name -> "span");
Instrumenter<Map<String, String>, Map<String, String>> instrumenter = builder.newInstrumenter();
Instrumenter<Map<String, String>, Map<String, String>> instrumenter =
Instrumenter.<Map<String, String>, Map<String, String>>builder(
otelTesting.getOpenTelemetry(), "test", name -> "span")
.setInstrumentationVersion("1.0")
.newInstrumenter();
Context context = instrumenter.start(Context.root(), Collections.emptyMap());
assertThat(Span.fromContext(context)).isNotNull();
@ -770,6 +771,30 @@ class InstrumenterTest {
InstrumentationLibraryInfo.create("test", "1.0"))));
}
@Test
void schemaUrl() {
Instrumenter<Map<String, String>, Map<String, String>> instrumenter =
Instrumenter.<Map<String, String>, Map<String, String>>builder(
otelTesting.getOpenTelemetry(), "test", name -> "span")
.setSchemaUrl("https://opentelemetry.io/schemas/1.0.0")
.newInstrumenter();
Context context = instrumenter.start(Context.root(), Collections.emptyMap());
assertThat(Span.fromContext(context)).isNotNull();
instrumenter.end(context, Collections.emptyMap(), Collections.emptyMap(), null);
InstrumentationLibraryInfo expectedLibraryInfo =
InstrumentationLibraryInfo.create("test", null, "https://opentelemetry.io/schemas/1.0.0");
otelTesting
.assertTraces()
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("span").hasInstrumentationLibraryInfo(expectedLibraryInfo)));
}
private static void validateInstrumentationTypeSpanPresent(SpanKey spanKey, Context context) {
Span span = Span.fromContext(context);