Browse Source

Stabilize instrumentation-api and introduce jApiCmp checks (#6566)

* Stabilize instrumentation-api and introduce jApiCmp checks

* Update

Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
Mateusz Rzeszutek 2 years ago
parent
commit
edf421ede5

+ 12 - 0
.github/workflows/reusable-assemble.yml

@@ -62,6 +62,18 @@ jobs:
             exit 1
           fi
 
+      - name: Check for jApiCmp diffs
+        run: |
+          if git diff --quiet
+          then
+            echo "No diff detected."
+          else
+            echo "Diff detected - did you run './gradlew jApiCmp'?"
+            echo $(git diff --name-only)
+            echo $(git diff)
+            exit 1
+          fi
+
       - name: Upload deadlock detector artifacts if any
         if: always()
         uses: actions/upload-artifact@v3

+ 14 - 0
RELEASING.md

@@ -58,6 +58,20 @@ and deadlocks.
     (note that if this is not a patch release then the change log on main may already be up-to-date,
     in which case no pull request will be created).
 
+## Update release versions in documentations
+
+After releasing is done, you need to first update the docs. This needs to happen after artifacts have propagated
+to Maven Central so should probably be done an hour or two after the release workflow finishes.
+
+```sh
+./gradlew japicmp -PapiBaseVersion=a.b.c -PapiNewVersion=x.y.z
+./gradlew --refresh-dependencies japicmp
+```
+
+Where `x.y.z` is the version just released and `a.b.c` is the previous version.
+
+Create a PR to mark the new release in docs on the main branch.
+
 ## Credentials
 
 Same as the core repo, see [opentelemetry-java/RELEASING.md#credentials](https://github.com/open-telemetry/opentelemetry-java/blob/main/RELEASING.md#credentials).

+ 56 - 27
conventions/src/main/kotlin/otel.japicmp-conventions.gradle.kts

@@ -1,4 +1,8 @@
+import japicmp.model.JApiChangeStatus
 import me.champeau.gradle.japicmp.JapicmpTask
+import me.champeau.gradle.japicmp.report.stdrules.RecordSeenMembersSetup
+import me.champeau.gradle.japicmp.report.stdrules.SourceCompatibleRule
+import me.champeau.gradle.japicmp.report.stdrules.UnchangedMemberRule
 
 plugins {
   base
@@ -43,36 +47,61 @@ fun findArtifact(version: String): File {
   }
 }
 
-tasks {
-  val jApiCmp by registering(JapicmpTask::class) {
-    dependsOn("jar")
+// generate the api diff report for any module that is stable
+if (project.findProperty("otel.stable") == "true") {
+  afterEvaluate {
+    tasks {
+      val jApiCmp by registering(JapicmpTask::class) {
+        dependsOn("jar")
 
-    // the japicmp "new" version is either the user-specified one, or the locally built jar.
-    val apiNewVersion: String? by project
-    val newArtifact = apiNewVersion?.let { findArtifact(it) }
-      ?: file(getByName<Jar>("jar").archiveFile)
-    newClasspath.from(files(newArtifact))
+        // the japicmp "new" version is either the user-specified one, or the locally built jar.
+        val apiNewVersion: String? by project
+        val newArtifact = apiNewVersion?.let { findArtifact(it) }
+          ?: file(getByName<Jar>("jar").archiveFile)
+        newClasspath.from(files(newArtifact))
 
-    // only output changes, not everything
-    onlyModified.set(true)
+        // only output changes, not everything
+        onlyModified.set(true)
 
-    // the japicmp "old" version is either the user-specified one, or the latest release.
-    val apiBaseVersion: String? by project
-    val baselineVersion = apiBaseVersion ?: latestReleasedVersion
-    oldClasspath.from(try {
-      files(findArtifact(baselineVersion))
-    } catch (e: Exception) {
-      // if we can't find the baseline artifact, this is probably one that's never been published before,
-      // so publish the whole API. We do that by flipping this flag, and comparing the current against nothing.
-      onlyModified.set(false)
-      files()
-    })
+        // the japicmp "old" version is either the user-specified one, or the latest release.
+        val apiBaseVersion: String? by project
+        val baselineVersion = apiBaseVersion ?: latestReleasedVersion
+        oldClasspath.from(
+          try {
+            files(findArtifact(baselineVersion))
+          } catch (e: Exception) {
+            // if we can't find the baseline artifact, this is probably one that's never been published before,
+            // so publish the whole API. We do that by flipping this flag, and comparing the current against nothing.
+            onlyModified.set(false)
+            files()
+          }
+        )
 
-    // this is needed so that we only consider the current artifact, and not dependencies
-    ignoreMissingClasses.set(true)
-    packageExcludes.addAll("*.internal", "*.internal.*")
-    val baseVersionString = if (apiBaseVersion == null) "latest" else baselineVersion
-    val newVersionString = if (apiNewVersion == null) "current" else apiNewVersion
-    txtOutputFile.set(file("$rootDir/docs/apidiffs/${newVersionString}_vs_$baseVersionString/${base.archivesName.get()}.txt"))
+        // Reproduce defaults from https://github.com/melix/japicmp-gradle-plugin/blob/09f52739ef1fccda6b4310cf3f4b19dc97377024/src/main/java/me/champeau/gradle/japicmp/report/ViolationsGenerator.java#L130
+        // only changing the BinaryIncompatibleRule to our custom one that allows new default methods
+        // on interfaces, and adding default implementations to interface methods previously
+        // abstract.
+        richReport {
+          addSetupRule(RecordSeenMembersSetup::class.java)
+          addRule(JApiChangeStatus.NEW, SourceCompatibleRule::class.java)
+          addRule(JApiChangeStatus.MODIFIED, SourceCompatibleRule::class.java)
+          addRule(JApiChangeStatus.UNCHANGED, UnchangedMemberRule::class.java)
+          addRule(SourceCompatibleRule::class.java)
+        }
+
+        // this is needed so that we only consider the current artifact, and not dependencies
+        ignoreMissingClasses.set(true)
+        packageExcludes.addAll("*.internal", "*.internal.*")
+        val baseVersionString = if (apiBaseVersion == null) "latest" else baselineVersion
+        txtOutputFile.set(
+          apiNewVersion?.let { file("$rootDir/docs/apidiffs/${apiNewVersion}_vs_$baselineVersion/${base.archivesName.get()}.txt") }
+            ?: file("$rootDir/docs/apidiffs/current_vs_$baseVersionString/${base.archivesName.get()}.txt")
+        )
+      }
+      // have the check task depend on the api comparison task, to make it more likely it will get used.
+      named("check") {
+        dependsOn(jApiCmp)
+      }
+    }
   }
 }

+ 111 - 0
docs/apidiffs/current_vs_latest/opentelemetry-instrumentation-api.txt

@@ -0,0 +1,111 @@
+Comparing source compatibility of  against 
++++  NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor  (not serializable)
+	+++  CLASS FILE FORMAT VERSION: 52.0 <- n.a.
+	+++  NEW SUPERCLASS: java.lang.Object
+	+++  NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor constant(io.opentelemetry.api.common.AttributeKey, java.lang.Object)
+	+++  NEW METHOD: PUBLIC(+) ABSTRACT(+) void onEnd(io.opentelemetry.api.common.AttributesBuilder, io.opentelemetry.context.Context, java.lang.Object, java.lang.Object, java.lang.Throwable)
+	+++  NEW METHOD: PUBLIC(+) ABSTRACT(+) void onStart(io.opentelemetry.api.common.AttributesBuilder, io.opentelemetry.context.Context, java.lang.Object)
++++  NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.instrumentation.api.instrumenter.ContextCustomizer  (not serializable)
+	+++  CLASS FILE FORMAT VERSION: 52.0 <- n.a.
+	+++  NEW SUPERCLASS: java.lang.Object
+	+++  NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.context.Context onStart(io.opentelemetry.context.Context, java.lang.Object, io.opentelemetry.api.common.Attributes)
+	+++  NEW ANNOTATION: java.lang.FunctionalInterface
++++  NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.instrumentation.api.instrumenter.ErrorCauseExtractor  (not serializable)
+	+++  CLASS FILE FORMAT VERSION: 52.0 <- n.a.
+	+++  NEW SUPERCLASS: java.lang.Object
+	+++  NEW METHOD: PUBLIC(+) ABSTRACT(+) java.lang.Throwable extract(java.lang.Throwable)
+	+++  NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.instrumentation.api.instrumenter.ErrorCauseExtractor jdk()
+	+++  NEW ANNOTATION: java.lang.FunctionalInterface
++++  NEW CLASS: PUBLIC(+) io.opentelemetry.instrumentation.api.instrumenter.Instrumenter  (not serializable)
+	+++  CLASS FILE FORMAT VERSION: 52.0 <- n.a.
+	+++  NEW SUPERCLASS: java.lang.Object
+	+++  NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder builder(io.opentelemetry.api.OpenTelemetry, java.lang.String, io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor)
+	+++  NEW METHOD: PUBLIC(+) void end(io.opentelemetry.context.Context, java.lang.Object, java.lang.Object, java.lang.Throwable)
+	+++  NEW METHOD: PUBLIC(+) boolean shouldStart(io.opentelemetry.context.Context, java.lang.Object)
+	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.context.Context start(io.opentelemetry.context.Context, java.lang.Object)
++++  NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder  (not serializable)
+	+++  CLASS FILE FORMAT VERSION: 52.0 <- n.a.
+	+++  NEW SUPERCLASS: java.lang.Object
+	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder addAttributesExtractor(io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor)
+	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder addAttributesExtractors(java.lang.Iterable)
+	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder addContextCustomizer(io.opentelemetry.instrumentation.api.instrumenter.ContextCustomizer)
+	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder addOperationListener(io.opentelemetry.instrumentation.api.instrumenter.OperationListener)
+	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder addOperationMetrics(io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics)
+	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder addSpanLinksExtractor(io.opentelemetry.instrumentation.api.instrumenter.SpanLinksExtractor)
+	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.instrumentation.api.instrumenter.Instrumenter buildClientInstrumenter(io.opentelemetry.context.propagation.TextMapSetter)
+	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.instrumentation.api.instrumenter.Instrumenter buildConsumerInstrumenter(io.opentelemetry.context.propagation.TextMapGetter)
+	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.instrumentation.api.instrumenter.Instrumenter buildInstrumenter()
+	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.instrumentation.api.instrumenter.Instrumenter buildInstrumenter(io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor)
+	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.instrumentation.api.instrumenter.Instrumenter buildProducerInstrumenter(io.opentelemetry.context.propagation.TextMapSetter)
+	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.instrumentation.api.instrumenter.Instrumenter buildServerInstrumenter(io.opentelemetry.context.propagation.TextMapGetter)
+	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder setEnabled(boolean)
+	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder setErrorCauseExtractor(io.opentelemetry.instrumentation.api.instrumenter.ErrorCauseExtractor)
+	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder setInstrumentationVersion(java.lang.String)
+	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder setSchemaUrl(java.lang.String)
+	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder setSpanStatusExtractor(io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor)
++++  NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan  (not serializable)
+	+++  CLASS FILE FORMAT VERSION: 52.0 <- n.a.
+	+++  NEW SUPERCLASS: java.lang.Object
+	+++  NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.trace.Span current()
+	+++  NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.trace.Span fromContext(io.opentelemetry.context.Context)
+	+++  NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.trace.Span fromContextOrNull(io.opentelemetry.context.Context)
+		+++  NEW ANNOTATION: javax.annotation.Nullable
++++  NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.instrumentation.api.instrumenter.OperationListener  (not serializable)
+	+++  CLASS FILE FORMAT VERSION: 52.0 <- n.a.
+	+++  NEW SUPERCLASS: java.lang.Object
+	+++  NEW METHOD: PUBLIC(+) ABSTRACT(+) void onEnd(io.opentelemetry.context.Context, io.opentelemetry.api.common.Attributes, long)
+	+++  NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.context.Context onStart(io.opentelemetry.context.Context, io.opentelemetry.api.common.Attributes, long)
++++  NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics  (not serializable)
+	+++  CLASS FILE FORMAT VERSION: 52.0 <- n.a.
+	+++  NEW SUPERCLASS: java.lang.Object
+	+++  NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.instrumentation.api.instrumenter.OperationListener create(io.opentelemetry.api.metrics.Meter)
+	+++  NEW ANNOTATION: java.lang.FunctionalInterface
++++  NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor  (not serializable)
+	+++  CLASS FILE FORMAT VERSION: 52.0 <- n.a.
+	+++  NEW SUPERCLASS: java.lang.Object
+	+++  NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor alwaysClient()
+	+++  NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor alwaysConsumer()
+	+++  NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor alwaysInternal()
+	+++  NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor alwaysProducer()
+	+++  NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor alwaysServer()
+	+++  NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.trace.SpanKind extract(java.lang.Object)
+	+++  NEW ANNOTATION: java.lang.FunctionalInterface
++++  NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.instrumentation.api.instrumenter.SpanLinksBuilder  (not serializable)
+	+++  CLASS FILE FORMAT VERSION: 52.0 <- n.a.
+	+++  NEW SUPERCLASS: java.lang.Object
+	+++  NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.instrumentation.api.instrumenter.SpanLinksBuilder addLink(io.opentelemetry.api.trace.SpanContext)
+	+++  NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.instrumentation.api.instrumenter.SpanLinksBuilder addLink(io.opentelemetry.api.trace.SpanContext, io.opentelemetry.api.common.Attributes)
++++  NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.instrumentation.api.instrumenter.SpanLinksExtractor  (not serializable)
+	+++  CLASS FILE FORMAT VERSION: 52.0 <- n.a.
+	+++  NEW SUPERCLASS: java.lang.Object
+	+++  NEW METHOD: PUBLIC(+) ABSTRACT(+) void extract(io.opentelemetry.instrumentation.api.instrumenter.SpanLinksBuilder, io.opentelemetry.context.Context, java.lang.Object)
+	+++  NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.instrumentation.api.instrumenter.SpanLinksExtractor extractFromRequest(io.opentelemetry.context.propagation.TextMapPropagator, io.opentelemetry.context.propagation.TextMapGetter)
+	+++  NEW ANNOTATION: java.lang.FunctionalInterface
++++  NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor  (not serializable)
+	+++  CLASS FILE FORMAT VERSION: 52.0 <- n.a.
+	+++  NEW SUPERCLASS: java.lang.Object
+	+++  NEW METHOD: PUBLIC(+) ABSTRACT(+) java.lang.String extract(java.lang.Object)
+	+++  NEW ANNOTATION: java.lang.FunctionalInterface
++++  NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.instrumentation.api.instrumenter.SpanStatusBuilder  (not serializable)
+	+++  CLASS FILE FORMAT VERSION: 52.0 <- n.a.
+	+++  NEW SUPERCLASS: java.lang.Object
+	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.instrumentation.api.instrumenter.SpanStatusBuilder setStatus(io.opentelemetry.api.trace.StatusCode)
+	+++  NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.instrumentation.api.instrumenter.SpanStatusBuilder setStatus(io.opentelemetry.api.trace.StatusCode, java.lang.String)
++++  NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor  (not serializable)
+	+++  CLASS FILE FORMAT VERSION: 52.0 <- n.a.
+	+++  NEW SUPERCLASS: java.lang.Object
+	+++  NEW METHOD: PUBLIC(+) ABSTRACT(+) void extract(io.opentelemetry.instrumentation.api.instrumenter.SpanStatusBuilder, java.lang.Object, java.lang.Object, java.lang.Throwable)
+	+++  NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor getDefault()
+	+++  NEW ANNOTATION: java.lang.FunctionalInterface
++++  NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.instrumentation.api.util.ClassNames  (not serializable)
+	+++  CLASS FILE FORMAT VERSION: 52.0 <- n.a.
+	+++  NEW SUPERCLASS: java.lang.Object
+	+++  NEW METHOD: PUBLIC(+) STATIC(+) java.lang.String simpleName(java.lang.Class)
++++  NEW CLASS: PUBLIC(+) ABSTRACT(+) io.opentelemetry.instrumentation.api.util.VirtualField  (not serializable)
+	+++  CLASS FILE FORMAT VERSION: 52.0 <- n.a.
+	+++  NEW SUPERCLASS: java.lang.Object
+	+++  NEW CONSTRUCTOR: PUBLIC(+) VirtualField()
+	+++  NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.instrumentation.api.util.VirtualField find(java.lang.Class, java.lang.Class)
+	+++  NEW METHOD: PUBLIC(+) ABSTRACT(+) java.lang.Object get(java.lang.Object)
+		+++  NEW ANNOTATION: javax.annotation.Nullable
+	+++  NEW METHOD: PUBLIC(+) ABSTRACT(+) void set(java.lang.Object, java.lang.Object)

+ 1 - 0
instrumentation-api/gradle.properties

@@ -0,0 +1 @@
+otel.stable=true

+ 3 - 3
instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/SpanSuppressionStrategy.java

@@ -8,9 +8,9 @@ package io.opentelemetry.instrumentation.api.instrumenter;
 import static java.util.Collections.singleton;
 
 import io.opentelemetry.api.trace.SpanKind;
-import io.opentelemetry.instrumentation.api.instrumenter.SpanSuppressor.BySpanKey;
-import io.opentelemetry.instrumentation.api.instrumenter.SpanSuppressor.DelegateBySpanKind;
-import io.opentelemetry.instrumentation.api.instrumenter.SpanSuppressor.Noop;
+import io.opentelemetry.instrumentation.api.instrumenter.SpanSuppressors.BySpanKey;
+import io.opentelemetry.instrumentation.api.instrumenter.SpanSuppressors.DelegateBySpanKind;
+import io.opentelemetry.instrumentation.api.instrumenter.SpanSuppressors.Noop;
 import io.opentelemetry.instrumentation.api.internal.SpanKey;
 import io.opentelemetry.instrumentation.api.internal.SpanKeyProvider;
 import java.util.EnumMap;

+ 0 - 71
instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/SpanSuppressor.java

@@ -8,81 +8,10 @@ package io.opentelemetry.instrumentation.api.instrumenter;
 import io.opentelemetry.api.trace.Span;
 import io.opentelemetry.api.trace.SpanKind;
 import io.opentelemetry.context.Context;
-import io.opentelemetry.instrumentation.api.internal.SpanKey;
-import java.util.Map;
-import java.util.Set;
 
 interface SpanSuppressor {
 
   Context storeInContext(Context context, SpanKind spanKind, Span span);
 
   boolean shouldSuppress(Context parentContext, SpanKind spanKind);
-
-  enum Noop implements SpanSuppressor {
-    INSTANCE;
-
-    @Override
-    public Context storeInContext(Context context, SpanKind spanKind, Span span) {
-      return context;
-    }
-
-    @Override
-    public boolean shouldSuppress(Context parentContext, SpanKind spanKind) {
-      return false;
-    }
-  }
-
-  final class DelegateBySpanKind implements SpanSuppressor {
-
-    private final Map<SpanKind, SpanSuppressor> delegates;
-
-    DelegateBySpanKind(Map<SpanKind, SpanSuppressor> delegates) {
-      this.delegates = delegates;
-    }
-
-    @Override
-    public Context storeInContext(Context context, SpanKind spanKind, Span span) {
-      SpanSuppressor delegate = delegates.get(spanKind);
-      if (delegate == null) {
-        return context;
-      }
-      return delegate.storeInContext(context, spanKind, span);
-    }
-
-    @Override
-    public boolean shouldSuppress(Context parentContext, SpanKind spanKind) {
-      SpanSuppressor delegate = delegates.get(spanKind);
-      if (delegate == null) {
-        return false;
-      }
-      return delegate.shouldSuppress(parentContext, spanKind);
-    }
-  }
-
-  final class BySpanKey implements SpanSuppressor {
-
-    private final Set<SpanKey> spanKeys;
-
-    BySpanKey(Set<SpanKey> spanKeys) {
-      this.spanKeys = spanKeys;
-    }
-
-    @Override
-    public Context storeInContext(Context context, SpanKind spanKind, Span span) {
-      for (SpanKey spanKey : spanKeys) {
-        context = spanKey.storeInContext(context, span);
-      }
-      return context;
-    }
-
-    @Override
-    public boolean shouldSuppress(Context parentContext, SpanKind spanKind) {
-      for (SpanKey spanKey : spanKeys) {
-        if (spanKey.fromContextOrNull(parentContext) == null) {
-          return false;
-        }
-      }
-      return true;
-    }
-  }
 }

+ 86 - 0
instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/SpanSuppressors.java

@@ -0,0 +1,86 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.api.instrumenter;
+
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.instrumentation.api.internal.SpanKey;
+import java.util.Map;
+import java.util.Set;
+
+final class SpanSuppressors {
+
+  private SpanSuppressors() {}
+
+  enum Noop implements SpanSuppressor {
+    INSTANCE;
+
+    @Override
+    public Context storeInContext(Context context, SpanKind spanKind, Span span) {
+      return context;
+    }
+
+    @Override
+    public boolean shouldSuppress(Context parentContext, SpanKind spanKind) {
+      return false;
+    }
+  }
+
+  static final class DelegateBySpanKind implements SpanSuppressor {
+
+    private final Map<SpanKind, SpanSuppressor> delegates;
+
+    DelegateBySpanKind(Map<SpanKind, SpanSuppressor> delegates) {
+      this.delegates = delegates;
+    }
+
+    @Override
+    public Context storeInContext(Context context, SpanKind spanKind, Span span) {
+      SpanSuppressor delegate = delegates.get(spanKind);
+      if (delegate == null) {
+        return context;
+      }
+      return delegate.storeInContext(context, spanKind, span);
+    }
+
+    @Override
+    public boolean shouldSuppress(Context parentContext, SpanKind spanKind) {
+      SpanSuppressor delegate = delegates.get(spanKind);
+      if (delegate == null) {
+        return false;
+      }
+      return delegate.shouldSuppress(parentContext, spanKind);
+    }
+  }
+
+  static final class BySpanKey implements SpanSuppressor {
+
+    private final Set<SpanKey> spanKeys;
+
+    BySpanKey(Set<SpanKey> spanKeys) {
+      this.spanKeys = spanKeys;
+    }
+
+    @Override
+    public Context storeInContext(Context context, SpanKind spanKind, Span span) {
+      for (SpanKey spanKey : spanKeys) {
+        context = spanKey.storeInContext(context, span);
+      }
+      return context;
+    }
+
+    @Override
+    public boolean shouldSuppress(Context parentContext, SpanKind spanKind) {
+      for (SpanKey spanKey : spanKeys) {
+        if (spanKey.fromContextOrNull(parentContext) == null) {
+          return false;
+        }
+      }
+      return true;
+    }
+  }
+}