Ver Fonte

Add support for schemaUrls auto-computed from `AttributesExtrator`s (#8864)

Mateusz Rzeszutek há 1 ano atrás
pai
commit
1947c0fe42

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

@@ -6,6 +6,7 @@
 package io.opentelemetry.instrumentation.api.instrumenter;
 
 import static java.util.Objects.requireNonNull;
+import static java.util.logging.Level.WARNING;
 
 import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import io.opentelemetry.api.OpenTelemetry;
@@ -22,11 +23,13 @@ import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil;
 import io.opentelemetry.instrumentation.api.internal.EmbeddedInstrumentationProperties;
 import io.opentelemetry.instrumentation.api.internal.InstrumenterBuilderAccess;
 import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil;
+import io.opentelemetry.instrumentation.api.internal.SchemaUrlProvider;
 import io.opentelemetry.instrumentation.api.internal.SpanKey;
 import io.opentelemetry.instrumentation.api.internal.SpanKeyProvider;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
+import java.util.logging.Logger;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import javax.annotation.Nullable;
@@ -39,6 +42,8 @@ import javax.annotation.Nullable;
  */
 public final class InstrumenterBuilder<REQUEST, RESPONSE> {
 
+  private static final Logger logger = Logger.getLogger(InstrumenterBuilder.class.getName());
+
   private static final SpanSuppressionStrategy spanSuppressionStrategy =
       SpanSuppressionStrategy.fromConfig(
           ConfigPropertiesUtil.getString(
@@ -285,6 +290,7 @@ public final class InstrumenterBuilder<REQUEST, RESPONSE> {
     if (instrumentationVersion != null) {
       tracerBuilder.setInstrumentationVersion(instrumentationVersion);
     }
+    String schemaUrl = getSchemaUrl();
     if (schemaUrl != null) {
       tracerBuilder.setSchemaUrl(schemaUrl);
     }
@@ -305,6 +311,7 @@ public final class InstrumenterBuilder<REQUEST, RESPONSE> {
     if (instrumentationVersion != null) {
       meterBuilder.setInstrumentationVersion(instrumentationVersion);
     }
+    String schemaUrl = getSchemaUrl();
     if (schemaUrl != null) {
       meterBuilder.setSchemaUrl(schemaUrl);
     }
@@ -316,6 +323,36 @@ public final class InstrumenterBuilder<REQUEST, RESPONSE> {
     return listeners;
   }
 
+  @Nullable
+  private String getSchemaUrl() {
+    // url set explicitly overrides url computed using attributes extractors
+    if (schemaUrl != null) {
+      return schemaUrl;
+    }
+    Set<String> computedSchemaUrls =
+        attributesExtractors.stream()
+            .filter(SchemaUrlProvider.class::isInstance)
+            .map(SchemaUrlProvider.class::cast)
+            .flatMap(
+                provider -> {
+                  String url = provider.internalGetSchemaUrl();
+                  return url == null ? Stream.of() : Stream.of(url);
+                })
+            .collect(Collectors.toSet());
+    switch (computedSchemaUrls.size()) {
+      case 0:
+        return null;
+      case 1:
+        return computedSchemaUrls.iterator().next();
+      default:
+        logger.log(
+            WARNING,
+            "Multiple schemaUrls were detected: {0}. The built Instrumenter will have no schemaUrl assigned.",
+            computedSchemaUrls);
+        return null;
+    }
+  }
+
   SpanSuppressor buildSpanSuppressor() {
     return spanSuppressionStrategy.create(getSpanKeysFromAttributesExtractors());
   }

+ 22 - 0
instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/SchemaUrlProvider.java

@@ -0,0 +1,22 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.api.internal;
+
+import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
+import javax.annotation.Nullable;
+
+/**
+ * Returns the OpenTelemetry schema URL associated with the {@link AttributesExtractor} that
+ * implements this interface.
+ *
+ * <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
+ * at any time.
+ */
+public interface SchemaUrlProvider {
+
+  @Nullable
+  String internalGetSchemaUrl();
+}

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

@@ -25,6 +25,7 @@ import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
 import io.opentelemetry.context.Context;
 import io.opentelemetry.context.ContextKey;
 import io.opentelemetry.context.propagation.TextMapGetter;
+import io.opentelemetry.instrumentation.api.internal.SchemaUrlProvider;
 import io.opentelemetry.instrumentation.api.internal.SpanKey;
 import io.opentelemetry.instrumentation.api.internal.SpanKeyProvider;
 import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
@@ -113,6 +114,30 @@ class InstrumenterTest {
     }
   }
 
+  static class AttributesExtractorWithSchemaUrl
+      implements AttributesExtractor<Map<String, String>, Map<String, String>>, SchemaUrlProvider {
+
+    @Override
+    public void onStart(
+        AttributesBuilder attributes, Context parentContext, Map<String, String> request) {
+      attributes.put("key", "value");
+    }
+
+    @Override
+    public void onEnd(
+        AttributesBuilder attributes,
+        Context context,
+        Map<String, String> request,
+        @Nullable Map<String, String> response,
+        @Nullable Throwable error) {}
+
+    @Nullable
+    @Override
+    public String internalGetSchemaUrl() {
+      return "schemaUrl from extractor";
+    }
+  }
+
   static class LinksExtractor implements SpanLinksExtractor<Map<String, String>> {
 
     @Override
@@ -583,11 +608,60 @@ class InstrumenterTest {
   }
 
   @Test
-  void schemaUrl() {
+  void schemaUrl_setExplicitly() {
+    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")
+            .buildInstrumenter();
+
+    Context context = instrumenter.start(Context.root(), emptyMap());
+    assertThat(Span.fromContext(context)).isNotNull();
+
+    instrumenter.end(context, emptyMap(), emptyMap(), null);
+
+    InstrumentationScopeInfo expectedLibraryInfo =
+        InstrumentationScopeInfo.builder("test")
+            .setSchemaUrl("https://opentelemetry.io/schemas/1.0.0")
+            .build();
+    otelTesting
+        .assertTraces()
+        .hasTracesSatisfyingExactly(
+            trace ->
+                trace.hasSpansSatisfyingExactly(
+                    span -> span.hasName("span").hasInstrumentationScopeInfo(expectedLibraryInfo)));
+  }
+
+  @Test
+  void schemaUrl_computedFromExtractors() {
+    Instrumenter<Map<String, String>, Map<String, String>> instrumenter =
+        Instrumenter.<Map<String, String>, Map<String, String>>builder(
+                otelTesting.getOpenTelemetry(), "test", name -> "span")
+            .addAttributesExtractor(new AttributesExtractorWithSchemaUrl())
+            .buildInstrumenter();
+
+    Context context = instrumenter.start(Context.root(), emptyMap());
+    assertThat(Span.fromContext(context)).isNotNull();
+
+    instrumenter.end(context, emptyMap(), emptyMap(), null);
+
+    InstrumentationScopeInfo expectedLibraryInfo =
+        InstrumentationScopeInfo.builder("test").setSchemaUrl("schemaUrl from extractor").build();
+    otelTesting
+        .assertTraces()
+        .hasTracesSatisfyingExactly(
+            trace ->
+                trace.hasSpansSatisfyingExactly(
+                    span -> span.hasName("span").hasInstrumentationScopeInfo(expectedLibraryInfo)));
+  }
+
+  @Test
+  void schemaUrl_schemaSetExplicitlyOverridesSchemaComputedFromExtractors() {
     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")
+            .addAttributesExtractor(new AttributesExtractorWithSchemaUrl())
             .buildInstrumenter();
 
     Context context = instrumenter.start(Context.root(), emptyMap());