diff --git a/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java b/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java
index c87fa540198..ec10e2eb172 100644
--- a/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java
+++ b/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java
@@ -36,7 +36,6 @@ class NoSharedInternalCodeTest {
"opentelemetry-exporter-logging",
"opentelemetry-exporter-logging-otlp",
"opentelemetry-exporter-prometheus",
- "opentelemetry-exporter-zipkin",
"opentelemetry-extension-trace-propagators",
"opentelemetry-opencensus-shim",
"opentelemetry-sdk-common",
diff --git a/api/all/src/main/java/io/opentelemetry/api/internal/InstrumentationUtil.java b/api/all/src/main/java/io/opentelemetry/api/impl/InstrumentationUtil.java
similarity index 74%
rename from api/all/src/main/java/io/opentelemetry/api/internal/InstrumentationUtil.java
rename to api/all/src/main/java/io/opentelemetry/api/impl/InstrumentationUtil.java
index 4f5c1e676bf..381e03fef3b 100644
--- a/api/all/src/main/java/io/opentelemetry/api/internal/InstrumentationUtil.java
+++ b/api/all/src/main/java/io/opentelemetry/api/impl/InstrumentationUtil.java
@@ -3,15 +3,19 @@
* SPDX-License-Identifier: Apache-2.0
*/
-package io.opentelemetry.api.internal;
+package io.opentelemetry.api.impl;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextKey;
import java.util.Objects;
/**
- * This class is internal and is hence not for public use. Its APIs are unstable and can change at
- * any time.
+ * Utility for suppressing instrumentation cycles between exporters which leverage various client
+ * libraries in their implementations and the otel java agent which instruments those client
+ * libraries.
+ *
+ *
This class is not intended for use by application developers. Its API is stable and will not
+ * be changed or removed in a backwards-incompatible manner.
*/
public final class InstrumentationUtil {
private static final ContextKey SUPPRESS_INSTRUMENTATION_KEY =
diff --git a/api/all/src/test/java/io/opentelemetry/api/internal/InstrumentationUtilTest.java b/api/all/src/test/java/io/opentelemetry/api/internal/InstrumentationUtilTest.java
index 066c906d875..fe235644692 100644
--- a/api/all/src/test/java/io/opentelemetry/api/internal/InstrumentationUtilTest.java
+++ b/api/all/src/test/java/io/opentelemetry/api/internal/InstrumentationUtilTest.java
@@ -8,6 +8,7 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import io.opentelemetry.api.impl.InstrumentationUtil;
import io.opentelemetry.context.Context;
import org.junit.jupiter.api.Test;
diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt
index 3fe1b53a43e..ee4d560df8c 100644
--- a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt
+++ b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt
@@ -1,4 +1,9 @@
Comparing source compatibility of opentelemetry-api-1.63.0-SNAPSHOT.jar against opentelemetry-api-1.62.0.jar
++++ NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.api.impl.InstrumentationUtil (not serializable)
+ +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a.
+ +++ NEW SUPERCLASS: java.lang.Object
+ +++ NEW METHOD: PUBLIC(+) STATIC(+) boolean shouldSuppressInstrumentation(io.opentelemetry.context.Context)
+ +++ NEW METHOD: PUBLIC(+) STATIC(+) void suppressInstrumentation(java.lang.Runnable)
*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.api.logs.LogRecordBuilder (not serializable)
=== CLASS FILE FORMAT VERSION: 52.0 <- 52.0
+++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.logs.LogRecordBuilder setAttribute(java.lang.String, io.opentelemetry.api.common.Value>)
diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/InstrumentationUtil.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/InstrumentationUtil.java
index 9a88fe85060..a40ea3a8664 100644
--- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/InstrumentationUtil.java
+++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/InstrumentationUtil.java
@@ -11,8 +11,8 @@
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*
- * @deprecated use {@link io.opentelemetry.api.internal.InstrumentationUtil} instead. This class
- * should be removed once instrumentation does not refer to it anymore.
+ * @deprecated use {@link io.opentelemetry.api.impl.InstrumentationUtil} instead. This class should
+ * be removed once instrumentation does not refer to it anymore.
*/
@Deprecated
public final class InstrumentationUtil {
@@ -25,7 +25,7 @@ private InstrumentationUtil() {}
* calls.
*/
public static void suppressInstrumentation(Runnable runnable) {
- io.opentelemetry.api.internal.InstrumentationUtil.suppressInstrumentation(runnable);
+ io.opentelemetry.api.impl.InstrumentationUtil.suppressInstrumentation(runnable);
}
/**
@@ -35,6 +35,6 @@ public static void suppressInstrumentation(Runnable runnable) {
* instrumentation.
*/
public static boolean shouldSuppressInstrumentation(Context context) {
- return io.opentelemetry.api.internal.InstrumentationUtil.shouldSuppressInstrumentation(context);
+ return io.opentelemetry.api.impl.InstrumentationUtil.shouldSuppressInstrumentation(context);
}
}
diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/metrics/LegacyExporterMetrics.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/metrics/LegacyExporterMetrics.java
index a68df15c2c9..235ab16fdaa 100644
--- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/metrics/LegacyExporterMetrics.java
+++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/metrics/LegacyExporterMetrics.java
@@ -60,8 +60,6 @@ public static boolean isSupportedType(StandardComponentId.ExporterType exporterT
case OTLP_GRPC_SPAN_EXPORTER:
case OTLP_HTTP_SPAN_EXPORTER:
case OTLP_HTTP_JSON_SPAN_EXPORTER:
- case ZIPKIN_HTTP_SPAN_EXPORTER:
- case ZIPKIN_HTTP_JSON_SPAN_EXPORTER:
case OTLP_GRPC_LOG_EXPORTER:
case OTLP_HTTP_LOG_EXPORTER:
case OTLP_HTTP_JSON_LOG_EXPORTER:
@@ -100,9 +98,6 @@ private static String getExporterName(StandardComponentId.ExporterType exporterT
case OTLP_HTTP_METRIC_EXPORTER:
case OTLP_HTTP_JSON_METRIC_EXPORTER:
return "otlp";
- case ZIPKIN_HTTP_SPAN_EXPORTER:
- case ZIPKIN_HTTP_JSON_SPAN_EXPORTER:
- return "zipkin";
case OTLP_GRPC_PROFILES_EXPORTER:
throw new IllegalArgumentException("Profiles are not supported");
}
@@ -118,12 +113,10 @@ private static String getTransportName(StandardComponentId.ExporterType exporter
case OTLP_HTTP_SPAN_EXPORTER:
case OTLP_HTTP_LOG_EXPORTER:
case OTLP_HTTP_METRIC_EXPORTER:
- case ZIPKIN_HTTP_SPAN_EXPORTER:
return "http";
case OTLP_HTTP_JSON_SPAN_EXPORTER:
case OTLP_HTTP_JSON_LOG_EXPORTER:
case OTLP_HTTP_JSON_METRIC_EXPORTER:
- case ZIPKIN_HTTP_JSON_SPAN_EXPORTER:
return "http-json";
case OTLP_GRPC_PROFILES_EXPORTER:
throw new IllegalArgumentException("Profiles are not supported");
diff --git a/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSender.java b/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSender.java
index 20374358317..fd5ac0f2f03 100644
--- a/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSender.java
+++ b/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSender.java
@@ -23,7 +23,7 @@
package io.opentelemetry.exporter.sender.okhttp.internal;
-import io.opentelemetry.api.internal.InstrumentationUtil;
+import io.opentelemetry.api.impl.InstrumentationUtil;
import io.opentelemetry.exporter.internal.RetryUtil;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.common.export.Compressor;
diff --git a/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpHttpSender.java b/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpHttpSender.java
index ad35eae4a60..f19bba6204c 100644
--- a/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpHttpSender.java
+++ b/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpHttpSender.java
@@ -5,7 +5,7 @@
package io.opentelemetry.exporter.sender.okhttp.internal;
-import io.opentelemetry.api.internal.InstrumentationUtil;
+import io.opentelemetry.api.impl.InstrumentationUtil;
import io.opentelemetry.exporter.internal.RetryUtil;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.common.export.Compressor;
diff --git a/exporters/sender/okhttp/src/test/java/io/opentelemetry/exporter/sender/okhttp/internal/AbstractOkHttpSuppressionTest.java b/exporters/sender/okhttp/src/test/java/io/opentelemetry/exporter/sender/okhttp/internal/AbstractOkHttpSuppressionTest.java
index 4787bb8a610..f143eb67965 100644
--- a/exporters/sender/okhttp/src/test/java/io/opentelemetry/exporter/sender/okhttp/internal/AbstractOkHttpSuppressionTest.java
+++ b/exporters/sender/okhttp/src/test/java/io/opentelemetry/exporter/sender/okhttp/internal/AbstractOkHttpSuppressionTest.java
@@ -7,7 +7,7 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
-import io.opentelemetry.api.internal.InstrumentationUtil;
+import io.opentelemetry.api.impl.InstrumentationUtil;
import io.opentelemetry.context.Context;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
diff --git a/exporters/zipkin/build.gradle.kts b/exporters/zipkin/build.gradle.kts
index bee1da945c4..79171a9b228 100644
--- a/exporters/zipkin/build.gradle.kts
+++ b/exporters/zipkin/build.gradle.kts
@@ -13,7 +13,6 @@ dependencies {
api("io.zipkin.reporter2:zipkin-reporter")
- implementation(project(":exporters:common"))
implementation(project(":sdk-extensions:autoconfigure-spi"))
compileOnly(project(":api:incubator"))
diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporter.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporter.java
index 34405dbbf14..d726a5d5655 100644
--- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporter.java
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporter.java
@@ -5,14 +5,14 @@
package io.opentelemetry.exporter.zipkin;
-import io.opentelemetry.api.internal.InstrumentationUtil;
+import io.opentelemetry.api.impl.InstrumentationUtil;
import io.opentelemetry.api.metrics.MeterProvider;
-import io.opentelemetry.exporter.internal.metrics.ExporterInstrumentation;
+import io.opentelemetry.exporter.zipkin.internal.ComponentId;
+import io.opentelemetry.exporter.zipkin.internal.ExporterInstrumentation;
+import io.opentelemetry.exporter.zipkin.internal.StandardComponentId;
+import io.opentelemetry.exporter.zipkin.internal.ThrottlingLogger;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.common.InternalTelemetryVersion;
-import io.opentelemetry.sdk.common.internal.ComponentId;
-import io.opentelemetry.sdk.common.internal.StandardComponentId;
-import io.opentelemetry.sdk.common.internal.ThrottlingLogger;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import java.io.IOException;
diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterBuilder.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterBuilder.java
index 68007dc442a..98dc68bbc66 100644
--- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterBuilder.java
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterBuilder.java
@@ -5,7 +5,6 @@
package io.opentelemetry.exporter.zipkin;
-import static io.opentelemetry.api.internal.Utils.checkArgument;
import static java.util.Objects.requireNonNull;
import io.opentelemetry.api.GlobalOpenTelemetry;
@@ -151,9 +150,10 @@ public ZipkinSpanExporterBuilder setEndpoint(String endpoint) {
*/
public ZipkinSpanExporterBuilder setCompression(String compressionMethod) {
requireNonNull(compressionMethod, "compressionMethod");
- checkArgument(
- compressionMethod.equals("gzip") || compressionMethod.equals("none"),
- "Unsupported compression method. Supported compression methods include: gzip, none.");
+ if (!compressionMethod.equals("gzip") && !compressionMethod.equals("none")) {
+ throw new IllegalArgumentException(
+ "Unsupported compression method. Supported compression methods include: gzip, none.");
+ }
this.compressionEnabled = compressionMethod.equals("gzip");
return this;
}
@@ -166,7 +166,9 @@ public ZipkinSpanExporterBuilder setCompression(String compressionMethod) {
*/
public ZipkinSpanExporterBuilder setReadTimeout(long timeout, TimeUnit unit) {
requireNonNull(unit, "unit");
- checkArgument(timeout >= 0, "timeout must be non-negative");
+ if (timeout < 0) {
+ throw new IllegalArgumentException("timeout must be non-negative");
+ }
long timeoutMillis = timeout == 0 ? Long.MAX_VALUE : unit.toMillis(timeout);
this.readTimeoutMillis = (int) Math.min(timeoutMillis, Integer.MAX_VALUE);
return this;
diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ComponentId.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ComponentId.java
new file mode 100644
index 00000000000..15d735df0cf
--- /dev/null
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ComponentId.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin.internal;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.annotation.Nullable;
+
+/**
+ * The component id used for SDK health metrics. This corresponds to the otel.component.name and
+ * otel.component.id semconv attributes.
+ *
+ * Copied from {@code io.opentelemetry.sdk.common.internal.ComponentId} to avoid shared internal
+ * code.
+ *
+ *
This class is internal and is hence not for public use. Its APIs are unstable and can change
+ * at any time.
+ */
+public abstract class ComponentId {
+
+ private ComponentId() {}
+
+ public abstract String getTypeName();
+
+ public abstract String getComponentName();
+
+ static class Lazy extends ComponentId {
+
+ private static final Map nextIdCounters = new ConcurrentHashMap<>();
+
+ private final String componentType;
+ @Nullable private volatile String componentName = null;
+
+ Lazy(String componentType) {
+ this.componentType = componentType;
+ }
+
+ @Override
+ public String getTypeName() {
+ return componentType;
+ }
+
+ @Override
+ public String getComponentName() {
+ if (componentName == null) {
+ synchronized (this) {
+ if (componentName == null) {
+ int id =
+ nextIdCounters
+ .computeIfAbsent(componentType, k -> new AtomicInteger(0))
+ .getAndIncrement();
+ componentName = componentType + "/" + id;
+ }
+ }
+ }
+ return componentName;
+ }
+ }
+
+ public static StandardComponentId generateLazy(StandardComponentId.ExporterType exporterType) {
+ return new StandardComponentId(exporterType);
+ }
+}
diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ExporterInstrumentation.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ExporterInstrumentation.java
new file mode 100644
index 00000000000..fc2f2fb8919
--- /dev/null
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ExporterInstrumentation.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin.internal;
+
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.common.AttributesBuilder;
+import io.opentelemetry.api.metrics.MeterProvider;
+import io.opentelemetry.sdk.common.InternalTelemetryVersion;
+import java.net.URI;
+import java.util.function.Supplier;
+
+/**
+ * Copied from {@code io.opentelemetry.exporter.internal.ExporterInstrumentation} to avoid shared
+ * internal code.
+ *
+ * This class is internal and is hence not for public use. Its APIs are unstable and can change
+ * at any time.
+ */
+public class ExporterInstrumentation {
+
+ private final ExporterMetrics implementation;
+
+ public ExporterInstrumentation(
+ InternalTelemetryVersion schema,
+ Supplier meterProviderSupplier,
+ StandardComponentId componentId,
+ URI endpoint) {
+
+ Signal signal = componentId.getStandardType().signal();
+ switch (schema) {
+ case LEGACY:
+ implementation =
+ LegacyExporterMetrics.isSupportedType()
+ ? new LegacyExporterMetrics(meterProviderSupplier, componentId.getStandardType())
+ : NoopExporterMetrics.INSTANCE;
+ break;
+ case LATEST:
+ implementation =
+ new SemConvExporterMetrics(
+ meterProviderSupplier, signal, componentId, extractServerAttributes(endpoint));
+ break;
+ default:
+ throw new IllegalStateException("Unhandled case: " + schema);
+ }
+ }
+
+ // visible for testing
+ static Attributes extractServerAttributes(URI httpEndpoint) {
+ AttributesBuilder builder = Attributes.builder();
+ String host = httpEndpoint.getHost();
+ if (host != null) {
+ builder.put(SemConvAttributes.SERVER_ADDRESS, host);
+ }
+ int port = httpEndpoint.getPort();
+ if (port == -1) {
+ String scheme = httpEndpoint.getScheme();
+ if ("https".equals(scheme)) {
+ port = 443;
+ } else if ("http".equals(scheme)) {
+ port = 80;
+ }
+ }
+ if (port != -1) {
+ builder.put(SemConvAttributes.SERVER_PORT, port);
+ }
+ return builder.build();
+ }
+
+ public Recording startRecordingExport(int itemCount) {
+ return new Recording(implementation.startRecordingExport(itemCount));
+ }
+
+ /**
+ * This class is internal and is hence not for public use. Its APIs are unstable and can change at
+ * any time.
+ */
+ public static class Recording {
+
+ private final ExporterMetrics.Recording delegate;
+
+ private Recording(ExporterMetrics.Recording delegate) {
+ this.delegate = delegate;
+ }
+
+ /** Callback to notify that the export was successful. */
+ public void finishSuccessful() {
+ delegate.finishSuccessful(buildRequestAttributes());
+ }
+
+ /**
+ * Callback to notify that the export has failed with the given {@link Throwable} as failure
+ * cause.
+ *
+ * @param failureCause the cause of the failure
+ */
+ public void finishFailed(Throwable failureCause) {
+ finishFailed(failureCause.getClass().getName());
+ }
+
+ /**
+ * Callback to notify that the export has failed.
+ *
+ * @param errorType a failure reason suitable for the error.type attribute
+ */
+ public void finishFailed(String errorType) {
+ delegate.finishFailed(errorType, buildRequestAttributes());
+ }
+
+ private static Attributes buildRequestAttributes() {
+ return Attributes.empty();
+ }
+ }
+}
diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ExporterMetrics.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ExporterMetrics.java
new file mode 100644
index 00000000000..7ef471fe126
--- /dev/null
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ExporterMetrics.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin.internal;
+
+import io.opentelemetry.api.common.Attributes;
+import javax.annotation.Nullable;
+
+/**
+ * Copied from {@code io.opentelemetry.exporter.internal.ExporterMetrics} to avoid shared internal
+ * code.
+ *
+ * This class is internal and is hence not for public use. Its APIs are unstable and can change
+ * at any time.
+ */
+public interface ExporterMetrics {
+
+ Recording startRecordingExport(int itemCount);
+
+ /**
+ * This class is internal and is hence not for public use. Its APIs are unstable and can change at
+ * any time.
+ */
+ abstract class Recording {
+
+ private boolean alreadyEnded = false;
+
+ protected Recording() {}
+
+ public final void finishSuccessful(Attributes requestAttributes) {
+ ensureEndedOnce();
+ doFinish(null, requestAttributes);
+ }
+
+ public final void finishFailed(String errorType, Attributes requestAttributes) {
+ ensureEndedOnce();
+ if (errorType == null || errorType.isEmpty()) {
+ throw new IllegalArgumentException("The export failed but no failure reason was provided");
+ }
+ doFinish(errorType, requestAttributes);
+ }
+
+ private void ensureEndedOnce() {
+ if (alreadyEnded) {
+ throw new IllegalStateException("Recording already ended");
+ }
+ alreadyEnded = true;
+ }
+
+ /**
+ * Invoked when the export has finished, either successfully or failed.
+ *
+ * @param errorType null if the export was successful, otherwise a failure reason suitable for
+ * the error.type attribute
+ * @param requestAttributes additional attributes to add to request metrics
+ */
+ protected abstract void doFinish(@Nullable String errorType, Attributes requestAttributes);
+ }
+}
diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/LegacyExporterMetrics.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/LegacyExporterMetrics.java
new file mode 100644
index 00000000000..9eae9e1f843
--- /dev/null
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/LegacyExporterMetrics.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin.internal;
+
+import static io.opentelemetry.api.common.AttributeKey.booleanKey;
+import static io.opentelemetry.api.common.AttributeKey.stringKey;
+
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.LongCounter;
+import io.opentelemetry.api.metrics.Meter;
+import io.opentelemetry.api.metrics.MeterProvider;
+import java.util.function.Supplier;
+import javax.annotation.Nullable;
+
+/**
+ * Implements health metrics for exporters which were defined prior to the standardization in
+ * semantic conventions.
+ *
+ *
Copied from {@code io.opentelemetry.exporter.internal.LegacyExporterMetrics} to avoid shared
+ * internal code.
+ *
+ *
This class is internal and is hence not for public use. Its APIs are unstable and can change
+ * at any time.
+ */
+public class LegacyExporterMetrics implements ExporterMetrics {
+
+ private static final AttributeKey ATTRIBUTE_KEY_TYPE = stringKey("type");
+ private static final AttributeKey ATTRIBUTE_KEY_SUCCESS = booleanKey("success");
+
+ private final Supplier meterProviderSupplier;
+ private final String exporterName;
+ private final String transportName;
+ private final Attributes seenAttrs;
+ private final Attributes successAttrs;
+ private final Attributes failedAttrs;
+
+ /** Access via {@link #seen()}. */
+ @Nullable private volatile LongCounter seen;
+
+ /** Access via {@link #exported()} . */
+ @Nullable private volatile LongCounter exported;
+
+ LegacyExporterMetrics(
+ Supplier meterProviderSupplier,
+ StandardComponentId.ExporterType exporterType) {
+ this.meterProviderSupplier = meterProviderSupplier;
+ this.exporterName = "zipkin";
+ this.transportName = getTransportName(exporterType);
+ this.seenAttrs = Attributes.builder().put(ATTRIBUTE_KEY_TYPE, "span").build();
+ this.successAttrs = this.seenAttrs.toBuilder().put(ATTRIBUTE_KEY_SUCCESS, true).build();
+ this.failedAttrs = this.seenAttrs.toBuilder().put(ATTRIBUTE_KEY_SUCCESS, false).build();
+ }
+
+ public static boolean isSupportedType() {
+ return true;
+ }
+
+ private static String getTransportName(StandardComponentId.ExporterType exporterType) {
+ switch (exporterType) {
+ case ZIPKIN_HTTP_SPAN_EXPORTER:
+ return "http";
+ case ZIPKIN_HTTP_JSON_SPAN_EXPORTER:
+ return "http-json";
+ }
+ throw new IllegalArgumentException("Not a supported exporter type: " + exporterType);
+ }
+
+ /** Record number of records seen. */
+ private void addSeen(long value) {
+ seen().add(value, seenAttrs);
+ }
+
+ /** Record number of records which successfully exported. */
+ private void addSuccess(long value) {
+ exported().add(value, successAttrs);
+ }
+
+ /** Record number of records which failed to export. */
+ private void addFailed(long value) {
+ exported().add(value, failedAttrs);
+ }
+
+ private LongCounter seen() {
+ LongCounter seen = this.seen;
+ if (seen == null || SemConvExporterMetrics.isNoop(seen)) {
+ seen = meter().counterBuilder(exporterName + ".exporter.seen").build();
+ this.seen = seen;
+ }
+ return seen;
+ }
+
+ private LongCounter exported() {
+ LongCounter exported = this.exported;
+ if (exported == null || SemConvExporterMetrics.isNoop(exported)) {
+ exported = meter().counterBuilder(exporterName + ".exporter.exported").build();
+ this.exported = exported;
+ }
+ return exported;
+ }
+
+ private Meter meter() {
+ MeterProvider meterProvider = meterProviderSupplier.get();
+ if (meterProvider == null) {
+ meterProvider = MeterProvider.noop();
+ }
+ return meterProvider.get("io.opentelemetry.exporters." + exporterName + "-" + transportName);
+ }
+
+ @Override
+ public ExporterMetrics.Recording startRecordingExport(int itemCount) {
+ return new Recording(itemCount);
+ }
+
+ private class Recording extends ExporterMetrics.Recording {
+
+ private final int itemCount;
+
+ private Recording(int itemCount) {
+ this.itemCount = itemCount;
+ addSeen(itemCount);
+ }
+
+ @Override
+ protected void doFinish(@Nullable String errorType, Attributes requestAttributes) {
+ if (errorType != null) {
+ addFailed(itemCount);
+ } else {
+ addSuccess(itemCount);
+ }
+ }
+ }
+}
diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/NoopExporterMetrics.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/NoopExporterMetrics.java
new file mode 100644
index 00000000000..abea8c9c45a
--- /dev/null
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/NoopExporterMetrics.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin.internal;
+
+import io.opentelemetry.api.common.Attributes;
+import javax.annotation.Nullable;
+
+/**
+ * Copied from {@code io.opentelemetry.exporter.internal.NoopExporterMetrics} to avoid shared
+ * internal code.
+ */
+class NoopExporterMetrics implements ExporterMetrics {
+
+ static final NoopExporterMetrics INSTANCE = new NoopExporterMetrics();
+
+ @Override
+ public Recording startRecordingExport(int itemCount) {
+ return new NoopRecording();
+ }
+
+ private static class NoopRecording extends Recording {
+
+ @Override
+ protected void doFinish(@Nullable String errorType, Attributes requestAttributes) {}
+ }
+}
diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/RateLimiter.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/RateLimiter.java
new file mode 100644
index 00000000000..03230f9ede2
--- /dev/null
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/RateLimiter.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin.internal;
+
+import io.opentelemetry.sdk.common.Clock;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * This class was taken from Jaeger java client.
+ * https://github.com/jaegertracing/jaeger-client-java/blob/master/jaeger-core/src/main/java/io/jaegertracing/internal/samplers/RateLimitingSampler.java
+ *
+ * Variables have been renamed for clarity.
+ *
+ *
Copied from {@code io.opentelemetry.sdk.common.internal.RateLimiter} to avoid shared internal
+ * code.
+ *
+ *
This class is internal and is hence not for public use. Its APIs are unstable and can change
+ * at any time.
+ */
+public class RateLimiter {
+ private final Clock clock;
+ private final double creditsPerNanosecond;
+ private final long maxBalance; // max balance in nano ticks
+ private final AtomicLong currentBalance; // last op nano time less remaining balance
+
+ /**
+ * Create a new RateLimiter with the provided parameters.
+ *
+ * @param creditsPerSecond How many credits to accrue per second.
+ * @param maxBalance The maximum balance that the limiter can hold, which corresponds to the rate
+ * that is being limited to.
+ * @param clock An implementation of the {@link Clock} interface.
+ */
+ public RateLimiter(double creditsPerSecond, double maxBalance, Clock clock) {
+ this.clock = clock;
+ this.creditsPerNanosecond = creditsPerSecond / 1.0e9;
+ this.maxBalance = (long) (maxBalance / creditsPerNanosecond);
+ this.currentBalance = new AtomicLong(clock.nanoTime() - this.maxBalance);
+ }
+
+ /**
+ * Check to see if the provided cost can be spent within the current limits. Will deduct the cost
+ * from the current balance if it can be spent.
+ */
+ public boolean trySpend(double itemCost) {
+ long cost = (long) (itemCost / creditsPerNanosecond);
+ long currentNanos;
+ long currentBalanceNanos;
+ long availableBalanceAfterWithdrawal;
+ do {
+ currentBalanceNanos = this.currentBalance.get();
+ currentNanos = clock.nanoTime();
+ long currentAvailableBalance = currentNanos - currentBalanceNanos;
+ if (currentAvailableBalance > maxBalance) {
+ currentAvailableBalance = maxBalance;
+ }
+ availableBalanceAfterWithdrawal = currentAvailableBalance - cost;
+ if (availableBalanceAfterWithdrawal < 0) {
+ return false;
+ }
+ } while (!this.currentBalance.compareAndSet(
+ currentBalanceNanos, currentNanos - availableBalanceAfterWithdrawal));
+ return true;
+ }
+}
diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/SemConvAttributes.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/SemConvAttributes.java
new file mode 100644
index 00000000000..14a39c7c0a7
--- /dev/null
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/SemConvAttributes.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin.internal;
+
+import io.opentelemetry.api.common.AttributeKey;
+
+/**
+ * Provides access to semantic convention attributes used within the SDK implementation. This avoids
+ * having to pull in semantic conventions as a dependency, which would easily collide and conflict
+ * with user-provided dependencies.
+ *
+ *
Copied from {@code io.opentelemetry.sdk.common.internal.SemConvAttributes} to avoid shared
+ * internal code.
+ *
+ *
This class is internal and is hence not for public use. Its APIs are unstable and can change
+ * at any time.
+ */
+public class SemConvAttributes {
+
+ private SemConvAttributes() {}
+
+ public static final AttributeKey OTEL_COMPONENT_TYPE =
+ AttributeKey.stringKey("otel.component.type");
+ public static final AttributeKey OTEL_COMPONENT_NAME =
+ AttributeKey.stringKey("otel.component.name");
+ public static final AttributeKey ERROR_TYPE = AttributeKey.stringKey("error.type");
+
+ public static final AttributeKey SERVER_ADDRESS =
+ AttributeKey.stringKey("server.address");
+ public static final AttributeKey SERVER_PORT = AttributeKey.longKey("server.port");
+}
diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/SemConvExporterMetrics.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/SemConvExporterMetrics.java
new file mode 100644
index 00000000000..18254fd21ec
--- /dev/null
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/SemConvExporterMetrics.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin.internal;
+
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.common.AttributesBuilder;
+import io.opentelemetry.api.metrics.DoubleHistogram;
+import io.opentelemetry.api.metrics.LongCounter;
+import io.opentelemetry.api.metrics.LongUpDownCounter;
+import io.opentelemetry.api.metrics.Meter;
+import io.opentelemetry.api.metrics.MeterProvider;
+import io.opentelemetry.sdk.common.Clock;
+import java.util.Collections;
+import java.util.function.Supplier;
+import javax.annotation.Nullable;
+
+/**
+ * Copied from {@code io.opentelemetry.exporter.internal.SemConvExporterMetrics} to avoid shared
+ * internal code.
+ *
+ * This class is internal and is hence not for public use. Its APIs are unstable and can change
+ * at any time.
+ */
+public class SemConvExporterMetrics implements ExporterMetrics {
+
+ private static final Clock CLOCK = Clock.getDefault();
+
+ private final Supplier meterProviderSupplier;
+ private final Signal signal;
+ private final ComponentId componentId;
+ private final Attributes additionalAttributes;
+
+ @Nullable private volatile LongUpDownCounter inflight = null;
+ @Nullable private volatile LongCounter exported = null;
+ @Nullable private volatile DoubleHistogram duration = null;
+ @Nullable private volatile Attributes allAttributes = null;
+
+ public SemConvExporterMetrics(
+ Supplier meterProviderSupplier,
+ Signal signal,
+ ComponentId componentId,
+ Attributes additionalAttributes) {
+ this.meterProviderSupplier = meterProviderSupplier;
+ this.componentId = componentId;
+ this.signal = signal;
+ this.additionalAttributes = additionalAttributes;
+ }
+
+ @Override
+ public ExporterMetrics.Recording startRecordingExport(int itemCount) {
+ return new Recording(itemCount);
+ }
+
+ private Meter meter() {
+ MeterProvider meterProvider = meterProviderSupplier.get();
+ if (meterProvider == null) {
+ meterProvider = MeterProvider.noop();
+ }
+ return meterProvider.get("io.opentelemetry.exporters." + componentId.getTypeName());
+ }
+
+ private Attributes allAttributes() {
+ // attributes are initialized lazily to trigger lazy initialization of the componentId
+ Attributes allAttributes = this.allAttributes;
+ if (allAttributes == null) {
+ AttributesBuilder builder = Attributes.builder();
+ builder.put(SemConvAttributes.OTEL_COMPONENT_TYPE, componentId.getTypeName());
+ builder.put(SemConvAttributes.OTEL_COMPONENT_NAME, componentId.getComponentName());
+ builder.putAll(additionalAttributes);
+ allAttributes = builder.build();
+ this.allAttributes = allAttributes;
+ }
+ return allAttributes;
+ }
+
+ private LongUpDownCounter inflight() {
+ LongUpDownCounter inflight = this.inflight;
+ if (inflight == null || isNoop(inflight)) {
+ String unit = signal.getMetricUnit();
+ inflight =
+ meter()
+ .upDownCounterBuilder(signal.getExporterMetricNamespace() + ".inflight")
+ .setUnit("{" + unit + "}")
+ .setDescription(
+ "The number of "
+ + unit
+ + "s which were passed to the exporter, but that have not been exported yet (neither successful, nor failed)")
+ .build();
+ this.inflight = inflight;
+ }
+ return inflight;
+ }
+
+ private LongCounter exported() {
+ LongCounter exported = this.exported;
+ if (exported == null || isNoop(exported)) {
+ String unit = signal.getMetricUnit();
+ exported =
+ meter()
+ .counterBuilder(signal.getExporterMetricNamespace() + ".exported")
+ .setUnit("{" + unit + "}")
+ .setDescription(
+ "The number of "
+ + unit
+ + "s for which the export has finished, either successful or failed")
+ .build();
+ this.exported = exported;
+ }
+ return exported;
+ }
+
+ private DoubleHistogram duration() {
+ DoubleHistogram duration = this.duration;
+ if (duration == null || isNoop(duration)) {
+ duration =
+ meter()
+ .histogramBuilder("otel.sdk.exporter.operation.duration")
+ .setUnit("s")
+ .setDescription("The duration of exporting a batch of telemetry records")
+ .setExplicitBucketBoundariesAdvice(Collections.emptyList())
+ .build();
+ this.duration = duration;
+ }
+ return duration;
+ }
+
+ private void incrementInflight(long count) {
+ inflight().add(count, allAttributes());
+ }
+
+ private void decrementInflight(long count) {
+ inflight().add(-count, allAttributes());
+ }
+
+ private void incrementExported(long count, @Nullable String errorType) {
+ exported().add(count, getAttributesWithPotentialError(errorType, Attributes.empty()));
+ }
+
+ static boolean isNoop(Object instrument) {
+ // This is a poor way to identify a Noop implementation, but the API doesn't provide a better
+ // way. Perhaps we could add a common "Noop" interface to allow for an instanceof check?
+ return instrument.getClass().getSimpleName().startsWith("Noop");
+ }
+
+ private Attributes getAttributesWithPotentialError(
+ @Nullable String errorType, Attributes additionalAttributes) {
+ Attributes attributes = allAttributes();
+ boolean errorPresent = errorType != null && !errorType.isEmpty();
+ if (errorPresent || !additionalAttributes.isEmpty()) {
+ AttributesBuilder builder = attributes.toBuilder();
+ if (errorPresent) {
+ builder.put(SemConvAttributes.ERROR_TYPE, errorType);
+ }
+ attributes = builder.putAll(additionalAttributes).build();
+ }
+ return attributes;
+ }
+
+ private void recordDuration(
+ double seconds, @Nullable String errorType, Attributes requestAttributes) {
+ duration().record(seconds, getAttributesWithPotentialError(errorType, requestAttributes));
+ }
+
+ private class Recording extends ExporterMetrics.Recording {
+
+ private final int itemCount;
+
+ private final long startNanoTime;
+
+ private Recording(int itemCount) {
+ this.itemCount = itemCount;
+ startNanoTime = CLOCK.nanoTime();
+ incrementInflight(itemCount);
+ }
+
+ @Override
+ protected void doFinish(@Nullable String errorType, Attributes requestAttributes) {
+ decrementInflight(itemCount);
+ incrementExported(itemCount, errorType);
+ long durationNanos = CLOCK.nanoTime() - startNanoTime;
+ recordDuration(durationNanos / 1_000_000_000.0, errorType, requestAttributes);
+ }
+ }
+}
diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/Signal.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/Signal.java
new file mode 100644
index 00000000000..30736dead3d
--- /dev/null
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/Signal.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin.internal;
+
+/**
+ * Copied from {@code io.opentelemetry.sdk.common.internal.Signal} to avoid shared internal code.
+ *
+ * This class is internal and is hence not for public use. Its APIs are unstable and can change
+ * at any time.
+ */
+public enum Signal {
+ SPAN("otel.sdk.exporter.span", "span");
+
+ private final String exporterMetricNamespace;
+ private final String metricUnit;
+
+ Signal(String exporterMetricNamespace, String metricUnit) {
+ this.exporterMetricNamespace = exporterMetricNamespace;
+ this.metricUnit = metricUnit;
+ }
+
+ public String getExporterMetricNamespace() {
+ return exporterMetricNamespace;
+ }
+
+ public String getMetricUnit() {
+ return metricUnit;
+ }
+}
diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/StandardComponentId.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/StandardComponentId.java
new file mode 100644
index 00000000000..608a361e9b7
--- /dev/null
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/StandardComponentId.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin.internal;
+
+/**
+ * A {@link ComponentId} where the component type is one of {@link ExporterType}.
+ *
+ *
Copied from {@code io.opentelemetry.sdk.common.internal.StandardComponentId} to avoid shared
+ * internal code.
+ *
+ *
This class is internal and is hence not for public use. Its APIs are unstable and can change
+ * at any time.
+ */
+public class StandardComponentId extends ComponentId.Lazy {
+
+ /**
+ * This class is internal and is hence not for public use. Its APIs are unstable and can change at
+ * any time.
+ */
+ public enum ExporterType {
+ ZIPKIN_HTTP_SPAN_EXPORTER("zipkin_http_span_exporter", Signal.SPAN),
+ /**
+ * Has the same semconv attribute value as ZIPKIN_HTTP_SPAN_EXPORTER, but we still use a
+ * different enum value for now because they produce separate legacy metrics.
+ */
+ ZIPKIN_HTTP_JSON_SPAN_EXPORTER("zipkin_http_span_exporter", Signal.SPAN);
+
+ final String value;
+ private final Signal signal;
+
+ ExporterType(String value, Signal signal) {
+ this.value = value;
+ this.signal = signal;
+ }
+
+ public Signal signal() {
+ return signal;
+ }
+ }
+
+ private final ExporterType standardType;
+
+ StandardComponentId(ExporterType standardType) {
+ super(standardType.value);
+ this.standardType = standardType;
+ }
+
+ public ExporterType getStandardType() {
+ return standardType;
+ }
+}
diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ThrottlingLogger.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ThrottlingLogger.java
new file mode 100644
index 00000000000..b5d8c808bc5
--- /dev/null
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ThrottlingLogger.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin.internal;
+
+import static java.util.concurrent.TimeUnit.MINUTES;
+
+import io.opentelemetry.sdk.common.Clock;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+/**
+ * Will limit the number of log messages emitted, so as not to spam when problems are happening.
+ *
+ *
Copied from {@code io.opentelemetry.sdk.common.internal.ThrottlingLogger} to avoid shared
+ * internal code.
+ *
+ *
This class is internal and is hence not for public use. Its APIs are unstable and can change
+ * at any time.
+ */
+public class ThrottlingLogger {
+ private static final double DEFAULT_RATE_LIMIT = 5;
+ private static final double DEFAULT_THROTTLED_RATE_LIMIT = 1;
+ private static final TimeUnit DEFAULT_RATE_TIME_UNIT = MINUTES;
+
+ private final Logger delegate;
+ private final AtomicBoolean throttled = new AtomicBoolean(false);
+ private final RateLimiter fastRateLimiter;
+ private final RateLimiter throttledRateLimiter;
+
+ private final double rateLimit;
+ private final double throttledRateLimit;
+ private final TimeUnit rateTimeUnit;
+
+ /** Create a new logger which will enforce a max number of messages per minute. */
+ public ThrottlingLogger(Logger delegate) {
+ Clock clock = Clock.getDefault();
+ this.delegate = delegate;
+ this.rateLimit = DEFAULT_RATE_LIMIT;
+ this.throttledRateLimit = DEFAULT_THROTTLED_RATE_LIMIT;
+ this.rateTimeUnit = DEFAULT_RATE_TIME_UNIT;
+ this.fastRateLimiter =
+ new RateLimiter(this.rateLimit / this.rateTimeUnit.toSeconds(1), this.rateLimit, clock);
+ this.throttledRateLimiter =
+ new RateLimiter(
+ this.throttledRateLimit / this.rateTimeUnit.toSeconds(1),
+ this.throttledRateLimit,
+ clock);
+ }
+
+ /** Log a message at the given level. */
+ public void log(Level level, String message) {
+ log(level, message, null);
+ }
+
+ /** Log a message at the given level with a throwable. */
+ public void log(Level level, String message, @Nullable Throwable throwable) {
+ if (!isLoggable(level)) {
+ return;
+ }
+ if (throttled.get()) {
+ if (throttledRateLimiter.trySpend(1.0)) {
+ doLog(level, message, throwable);
+ }
+ return;
+ }
+
+ if (fastRateLimiter.trySpend(1.0)) {
+ doLog(level, message, throwable);
+ return;
+ }
+
+ if (throttled.compareAndSet(false, true)) {
+ // spend the balance in the throttled one, so that it starts at zero.
+ throttledRateLimiter.trySpend(throttledRateLimit);
+ String timeUnitString = rateTimeUnit.toString().toLowerCase(Locale.ROOT);
+ String throttleMessage =
+ String.format(
+ Locale.ROOT,
+ "Too many log messages detected. Will only log %.0f time(s) per %s from now on.",
+ throttledRateLimit,
+ timeUnitString.substring(0, timeUnitString.length() - 1));
+ delegate.log(level, throttleMessage);
+ doLog(level, message, throwable);
+ }
+ }
+
+ private void doLog(Level level, String message, @Nullable Throwable throwable) {
+ if (throwable != null) {
+ delegate.log(level, message, throwable);
+ } else {
+ delegate.log(level, message);
+ }
+ }
+
+ /**
+ * Returns whether the current wrapped logger is set to log at the given level.
+ *
+ * @return true if the logger set to log at the requested level.
+ */
+ public boolean isLoggable(Level level) {
+ return delegate.isLoggable(level);
+ }
+}
diff --git a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterTest.java b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterTest.java
index 463b81fc953..2227c8ff76a 100644
--- a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterTest.java
+++ b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterTest.java
@@ -15,7 +15,7 @@
import static org.mockito.Mockito.when;
import io.github.netmikey.logunit.api.LogCapturer;
-import io.opentelemetry.api.internal.InstrumentationUtil;
+import io.opentelemetry.api.impl.InstrumentationUtil;
import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.context.Context;
import io.opentelemetry.internal.testing.slf4j.SuppressLogger;
diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/common/internal/StandardComponentId.java b/sdk/common/src/main/java/io/opentelemetry/sdk/common/internal/StandardComponentId.java
index 17cc94fe36f..c9716bdfc15 100644
--- a/sdk/common/src/main/java/io/opentelemetry/sdk/common/internal/StandardComponentId.java
+++ b/sdk/common/src/main/java/io/opentelemetry/sdk/common/internal/StandardComponentId.java
@@ -27,13 +27,6 @@ public enum ExporterType {
OTLP_GRPC_METRIC_EXPORTER("otlp_grpc_metric_exporter", Signal.METRIC),
OTLP_HTTP_METRIC_EXPORTER("otlp_http_metric_exporter", Signal.METRIC),
OTLP_HTTP_JSON_METRIC_EXPORTER("otlp_http_json_metric_exporter", Signal.METRIC),
- ZIPKIN_HTTP_SPAN_EXPORTER("zipkin_http_span_exporter", Signal.SPAN),
- /**
- * Has the same semconv attribute value as ZIPKIN_HTTP_SPAN_EXPORTER, but we still use a
- * different enum value for now because they produce separate legacy metrics.
- */
- ZIPKIN_HTTP_JSON_SPAN_EXPORTER("zipkin_http_span_exporter", Signal.SPAN),
-
OTLP_GRPC_PROFILES_EXPORTER("TBD", Signal.PROFILE); // TODO: not yet standardized in semconv
final String value;