Переглянути джерело

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>
Mateusz Rzeszutek 2 роки тому
батько
коміт
2ce1162eac

+ 25 - 0
docs/contributing/using-instrumenter-api.md

@@ -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

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

@@ -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();
   }
 
   /**

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

@@ -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);

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

@@ -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);