localIpAddressSupplier = LocalInetAddressSupplier.getInstance();
@@ -243,4 +249,12 @@ public ZipkinSpanExporter build() {
endpoint,
transformer);
}
+
+ // Copied from io.opentelemetry.api.internal.Utils to remove internal dependencies as part of
+ // https://github.com/open-telemetry/opentelemetry-java/issues/7863
+ private static void checkArgument(boolean isValid, String errorMessage) {
+ if (!isValid) {
+ throw new IllegalArgumentException(errorMessage);
+ }
+ }
}
diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ZipkinSpanExporterProvider.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ZipkinSpanExporterProvider.java
index cbd55d386dc..f73446e6e40 100644
--- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ZipkinSpanExporterProvider.java
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ZipkinSpanExporterProvider.java
@@ -5,19 +5,19 @@
package io.opentelemetry.exporter.zipkin.internal;
-import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter;
-import io.opentelemetry.exporter.zipkin.ZipkinSpanExporterBuilder;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import java.time.Duration;
/**
- * {@link SpanExporter} SPI implementation for {@link ZipkinSpanExporter}.
+ * {@link SpanExporter} SPI implementation for {@link
+ * io.opentelemetry.exporter.zipkin.ZipkinSpanExporter}.
*
* This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
+@SuppressWarnings("deprecation")
public class ZipkinSpanExporterProvider implements ConfigurableSpanExporterProvider {
@Override
public String getName() {
@@ -26,7 +26,8 @@ public String getName() {
@Override
public SpanExporter createExporter(ConfigProperties config) {
- ZipkinSpanExporterBuilder builder = ZipkinSpanExporter.builder();
+ io.opentelemetry.exporter.zipkin.ZipkinSpanExporterBuilder builder =
+ io.opentelemetry.exporter.zipkin.ZipkinSpanExporter.builder();
String endpoint = config.getString("otel.exporter.zipkin.endpoint");
if (endpoint != null) {
diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/ComponentId.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/ComponentId.java
new file mode 100644
index 00000000000..bbd6280b459
--- /dev/null
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/ComponentId.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin.internal.copied;
+
+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.
+ *
+ *
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 ComponentId generateLazy(String componentType) {
+ return new Lazy(componentType);
+ }
+
+ public static StandardComponentId generateLazy(StandardComponentId.ExporterType exporterType) {
+ return new StandardComponentId(exporterType);
+ }
+}
diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/ExporterInstrumentation.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/ExporterInstrumentation.java
new file mode 100644
index 00000000000..1f123978264
--- /dev/null
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/ExporterInstrumentation.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin.internal.copied;
+
+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.net.URISyntaxException;
+import java.util.function.Supplier;
+import javax.annotation.Nullable;
+
+/**
+ * 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,
+ String endpoint) {
+
+ Signal signal = componentId.getStandardType().signal();
+ switch (schema) {
+ case LEGACY:
+ implementation =
+ LegacyExporterMetrics.isSupportedType(componentId.getStandardType())
+ ? new LegacyExporterMetrics(meterProviderSupplier, componentId.getStandardType())
+ : NoopExporterMetrics.INSTANCE;
+ break;
+ case LATEST:
+ implementation =
+ signal == Signal.PROFILE
+ ? NoopExporterMetrics.INSTANCE
+ : new SemConvExporterMetrics(
+ meterProviderSupplier, signal, componentId, extractServerAttributes(endpoint));
+ break;
+ default:
+ throw new IllegalStateException("Unhandled case: " + schema);
+ }
+ }
+
+ // visible for testing
+ static Attributes extractServerAttributes(String httpEndpoint) {
+ try {
+ URI parsed = new URI(httpEndpoint);
+ AttributesBuilder builder = Attributes.builder();
+ String host = parsed.getHost();
+ if (host != null) {
+ builder.put(SemConvAttributes.SERVER_ADDRESS, host);
+ }
+ int port = parsed.getPort();
+ if (port == -1) {
+ String scheme = parsed.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();
+ } catch (URISyntaxException e) {
+ return Attributes.empty();
+ }
+ }
+
+ 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;
+ @Nullable private Long httpStatusCode;
+ @Nullable private Long grpcStatusCode;
+
+ private Recording(ExporterMetrics.Recording delegate) {
+ this.delegate = delegate;
+ }
+
+ public void setHttpStatusCode(long httpStatusCode) {
+ if (grpcStatusCode != null) {
+ throw new IllegalStateException(
+ "gRPC status code already set, can only set either gRPC or HTTP");
+ }
+ this.httpStatusCode = httpStatusCode;
+ }
+
+ public void setGrpcStatusCode(long grpcStatusCode) {
+ if (httpStatusCode != null) {
+ throw new IllegalStateException(
+ "HTTP status code already set, can only set either gRPC or HTTP");
+ }
+ this.grpcStatusCode = grpcStatusCode;
+ }
+
+ /** 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 Attributes buildRequestAttributes() {
+ if (httpStatusCode != null) {
+ return Attributes.of(SemConvAttributes.HTTP_RESPONSE_STATUS_CODE, httpStatusCode);
+ }
+ if (grpcStatusCode != null) {
+ return Attributes.of(SemConvAttributes.RPC_GRPC_STATUS_CODE, grpcStatusCode);
+ }
+ return Attributes.empty();
+ }
+ }
+}
diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/ExporterMetrics.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/ExporterMetrics.java
new file mode 100644
index 00000000000..954f218a981
--- /dev/null
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/ExporterMetrics.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin.internal.copied;
+
+import io.opentelemetry.api.common.Attributes;
+import javax.annotation.Nullable;
+
+/**
+ * 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/copied/InstrumentationUtil.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/InstrumentationUtil.java
new file mode 100644
index 00000000000..4eaa8117fb6
--- /dev/null
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/InstrumentationUtil.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin.internal.copied;
+
+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.
+ */
+public final class InstrumentationUtil {
+ private static final ContextKey SUPPRESS_INSTRUMENTATION_KEY =
+ ContextKey.named("suppress_instrumentation");
+
+ private InstrumentationUtil() {}
+
+ /**
+ * Adds a Context boolean key that will allow to identify HTTP calls coming from OTel exporters.
+ * The key later be checked by an automatic instrumentation to avoid tracing OTel exporter's
+ * calls.
+ */
+ public static void suppressInstrumentation(Runnable runnable) {
+ Context.current().with(SUPPRESS_INSTRUMENTATION_KEY, true).wrap(runnable).run();
+ }
+
+ /**
+ * Checks if an automatic instrumentation should be suppressed with the provided Context.
+ *
+ * @return TRUE to suppress the automatic instrumentation, FALSE to continue with the
+ * instrumentation.
+ */
+ public static boolean shouldSuppressInstrumentation(Context context) {
+ return Objects.equals(context.get(SUPPRESS_INSTRUMENTATION_KEY), true);
+ }
+}
diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/LegacyExporterMetrics.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/LegacyExporterMetrics.java
new file mode 100644
index 00000000000..09660485e63
--- /dev/null
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/LegacyExporterMetrics.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin.internal.copied;
+
+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.
+ *
+ * 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 = getExporterName(exporterType);
+ this.transportName = getTransportName(exporterType);
+ this.seenAttrs =
+ Attributes.builder().put(ATTRIBUTE_KEY_TYPE, getTypeString(exporterType.signal())).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(StandardComponentId.ExporterType exporterType) {
+ switch (exporterType) {
+ 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:
+ case OTLP_GRPC_METRIC_EXPORTER:
+ case OTLP_HTTP_METRIC_EXPORTER:
+ case OTLP_HTTP_JSON_METRIC_EXPORTER:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private static String getTypeString(Signal signal) {
+ switch (signal) {
+ case SPAN:
+ return "span";
+ case LOG:
+ return "log";
+ case METRIC:
+ return "metric";
+ case PROFILE:
+ throw new IllegalArgumentException("Profiles are not supported");
+ }
+ throw new IllegalArgumentException("Unhandled signal type: " + signal);
+ }
+
+ private static String getExporterName(StandardComponentId.ExporterType exporterType) {
+ switch (exporterType) {
+ case OTLP_GRPC_SPAN_EXPORTER:
+ case OTLP_HTTP_SPAN_EXPORTER:
+ case OTLP_HTTP_JSON_SPAN_EXPORTER:
+ case OTLP_GRPC_LOG_EXPORTER:
+ case OTLP_HTTP_LOG_EXPORTER:
+ case OTLP_HTTP_JSON_LOG_EXPORTER:
+ case OTLP_GRPC_METRIC_EXPORTER:
+ 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");
+ }
+ throw new IllegalArgumentException("Not a supported exporter type: " + exporterType);
+ }
+
+ private static String getTransportName(StandardComponentId.ExporterType exporterType) {
+ switch (exporterType) {
+ case OTLP_GRPC_SPAN_EXPORTER:
+ case OTLP_GRPC_LOG_EXPORTER:
+ case OTLP_GRPC_METRIC_EXPORTER:
+ return "grpc";
+ 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");
+ }
+ 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/copied/NoopExporterMetrics.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/NoopExporterMetrics.java
new file mode 100644
index 00000000000..975dbac15a7
--- /dev/null
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/NoopExporterMetrics.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin.internal.copied;
+
+import io.opentelemetry.api.common.Attributes;
+import javax.annotation.Nullable;
+
+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/copied/README.md b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/README.md
new file mode 100644
index 00000000000..5bd237f67ae
--- /dev/null
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/README.md
@@ -0,0 +1,3 @@
+All classes in this package were copied from elsewhere with the project. This was done to eliminate
+dependencies on any class in an `*.internal.*` package. See
+https://github.com/open-telemetry/opentelemetry-java/issues/7863.
diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/RateLimiter.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/RateLimiter.java
new file mode 100644
index 00000000000..f72a3b171f1
--- /dev/null
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/RateLimiter.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin.internal.copied;
+
+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.
+ *
+ *
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/copied/SemConvAttributes.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/SemConvAttributes.java
new file mode 100644
index 00000000000..214258538de
--- /dev/null
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/SemConvAttributes.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin.internal.copied;
+
+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.
+ *
+ *
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");
+
+ public static final AttributeKey RPC_GRPC_STATUS_CODE =
+ AttributeKey.longKey("rpc.grpc.status_code");
+ public static final AttributeKey HTTP_RESPONSE_STATUS_CODE =
+ AttributeKey.longKey("http.response.status_code");
+
+ public static final AttributeKey OTEL_SPAN_PARENT_ORIGIN =
+ AttributeKey.stringKey("otel.span.parent.origin");
+ public static final AttributeKey OTEL_SPAN_SAMPLING_RESULT =
+ AttributeKey.stringKey("otel.span.sampling_result");
+}
diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/SemConvExporterMetrics.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/SemConvExporterMetrics.java
new file mode 100644
index 00000000000..13b30b8a2df
--- /dev/null
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/SemConvExporterMetrics.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin.internal.copied;
+
+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;
+
+/**
+ * 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/copied/Signal.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/Signal.java
new file mode 100644
index 00000000000..8ceae7a8dc5
--- /dev/null
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/Signal.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin.internal.copied;
+
+import java.util.Locale;
+
+/**
+ * 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"),
+ METRIC("otel.sdk.exporter.metric_data_point", "data_point"),
+ LOG("otel.sdk.exporter.log", "log_record"),
+ PROFILE("TBD", "TBD");
+
+ private final String exporterMetricNamespace;
+ private final String metricUnit;
+
+ Signal(String exporterMetricNamespace, String metricUnit) {
+ this.exporterMetricNamespace = exporterMetricNamespace;
+ this.metricUnit = metricUnit;
+ }
+
+ public String logFriendlyName() {
+ return name().toLowerCase(Locale.ENGLISH);
+ }
+
+ public String getExporterMetricNamespace() {
+ return exporterMetricNamespace;
+ }
+
+ public String getMetricUnit() {
+ return metricUnit;
+ }
+}
diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/StandardComponentId.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/StandardComponentId.java
new file mode 100644
index 00000000000..f193da80721
--- /dev/null
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/StandardComponentId.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin.internal.copied;
+
+/**
+ * A {@link ComponentId} where the component type is one of {@link ExporterType}.
+ *
+ * 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 {
+ OTLP_GRPC_SPAN_EXPORTER("otlp_grpc_span_exporter", Signal.SPAN),
+ OTLP_HTTP_SPAN_EXPORTER("otlp_http_span_exporter", Signal.SPAN),
+ OTLP_HTTP_JSON_SPAN_EXPORTER("otlp_http_json_span_exporter", Signal.SPAN),
+ OTLP_GRPC_LOG_EXPORTER("otlp_grpc_log_exporter", Signal.LOG),
+ OTLP_HTTP_LOG_EXPORTER("otlp_http_log_exporter", Signal.LOG),
+ OTLP_HTTP_JSON_LOG_EXPORTER("otlp_http_json_log_exporter", Signal.LOG),
+ 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;
+ 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/copied/ThrottlingLogger.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/ThrottlingLogger.java
new file mode 100644
index 00000000000..d6ffd23147a
--- /dev/null
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/copied/ThrottlingLogger.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin.internal.copied;
+
+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.
+ *
+ *
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) {
+ this(delegate, Clock.getDefault());
+ }
+
+ /** Alternate way to create logger that allows setting custom intervals and units. * */
+ public ThrottlingLogger(
+ Logger delegate, double rateLimit, double throttledRateLimit, TimeUnit rateTimeUnit) {
+ this(delegate, Clock.getDefault(), rateLimit, throttledRateLimit, rateTimeUnit);
+ }
+
+ // visible for testing
+ ThrottlingLogger(Logger delegate, Clock clock) {
+ this(delegate, clock, DEFAULT_RATE_LIMIT, DEFAULT_THROTTLED_RATE_LIMIT, DEFAULT_RATE_TIME_UNIT);
+ }
+
+ ThrottlingLogger(
+ Logger delegate,
+ Clock clock,
+ double rateLimit,
+ double throttledRateLimit,
+ TimeUnit rateTimeUnit) {
+ this.delegate = delegate;
+ this.rateLimit = rateLimit;
+ this.throttledRateLimit = throttledRateLimit;
+ this.rateTimeUnit = rateTimeUnit;
+ 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/ZipkinSpanExporterEndToEndHttpTest.java b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterEndToEndHttpTest.java
index 4ad9bdfb106..38fb9ee078e 100644
--- a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterEndToEndHttpTest.java
+++ b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterEndToEndHttpTest.java
@@ -50,6 +50,7 @@
import zipkin2.reporter.SpanBytesEncoder;
import zipkin2.reporter.okhttp3.OkHttpSender;
+@SuppressWarnings("deprecation") // testing deprecated code
@Testcontainers(disabledWithoutDocker = true)
class ZipkinSpanExporterEndToEndHttpTest {
private static final WebClient client = WebClient.of();
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 759cfae782b..b0cb1ba55e0 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,9 +15,9 @@
import static org.mockito.Mockito.when;
import io.github.netmikey.logunit.api.LogCapturer;
-import io.opentelemetry.api.internal.InstrumentationUtil;
import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.context.Context;
+import io.opentelemetry.exporter.zipkin.internal.copied.InstrumentationUtil;
import io.opentelemetry.internal.testing.slf4j.SuppressLogger;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.common.InternalTelemetryVersion;
@@ -41,6 +41,7 @@
import zipkin2.reporter.SpanBytesEncoder;
@ExtendWith(MockitoExtension.class)
+@SuppressWarnings("deprecation") // testing deprecated code
class ZipkinSpanExporterTest {
@Mock private BytesMessageSender mockSender;
diff --git a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/internal/ZipkinSpanExporterProviderTest.java b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/internal/ZipkinSpanExporterProviderTest.java
index 289fa1f7709..814d2d967fc 100644
--- a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/internal/ZipkinSpanExporterProviderTest.java
+++ b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/internal/ZipkinSpanExporterProviderTest.java
@@ -7,7 +7,6 @@
import static org.assertj.core.api.Assertions.assertThat;
-import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter;
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import java.util.Collections;
@@ -15,6 +14,7 @@
import java.util.Map;
import org.junit.jupiter.api.Test;
+@SuppressWarnings("deprecation") // testing deprecated code
class ZipkinSpanExporterProviderTest {
private static final ZipkinSpanExporterProvider provider = new ZipkinSpanExporterProvider();
@@ -28,7 +28,8 @@ void getName() {
void createExporter_Default() {
try (SpanExporter spanExporter =
provider.createExporter(DefaultConfigProperties.createFromMap(Collections.emptyMap()))) {
- assertThat(spanExporter).isInstanceOf(ZipkinSpanExporter.class);
+ assertThat(spanExporter)
+ .isInstanceOf(io.opentelemetry.exporter.zipkin.ZipkinSpanExporter.class);
assertThat(spanExporter)
.extracting("sender")
.extracting("delegate")
@@ -50,7 +51,8 @@ void createExporter_WithConfiguration() {
try (SpanExporter spanExporter =
provider.createExporter(DefaultConfigProperties.createFromMap(config))) {
- assertThat(spanExporter).isInstanceOf(ZipkinSpanExporter.class);
+ assertThat(spanExporter)
+ .isInstanceOf(io.opentelemetry.exporter.zipkin.ZipkinSpanExporter.class);
assertThat(spanExporter)
.extracting("sender")
.extracting("delegate")
diff --git a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/internal/copied/ExporterInstrumentationTest.java b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/internal/copied/ExporterInstrumentationTest.java
new file mode 100644
index 00000000000..f7eb70d6df5
--- /dev/null
+++ b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/internal/copied/ExporterInstrumentationTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin.internal.copied;
+
+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import io.opentelemetry.api.metrics.MeterProvider;
+import io.opentelemetry.sdk.common.CompletableResultCode;
+import io.opentelemetry.sdk.common.InternalTelemetryVersion;
+import io.opentelemetry.sdk.metrics.InstrumentType;
+import io.opentelemetry.sdk.metrics.SdkMeterProvider;
+import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
+import io.opentelemetry.sdk.metrics.export.CollectionRegistration;
+import io.opentelemetry.sdk.metrics.export.MetricReader;
+import java.util.function.Supplier;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.mockito.Mockito;
+
+class ExporterInstrumentationTest {
+
+ @SuppressWarnings("unchecked")
+ Supplier meterProviderSupplier = mock(Supplier.class);
+
+ @ParameterizedTest
+ @EnumSource
+ void validMeterProvider(InternalTelemetryVersion schemaVersion) {
+ when(meterProviderSupplier.get())
+ .thenReturn(
+ SdkMeterProvider.builder()
+ // Have to provide a valid reader.
+ .registerMetricReader(
+ new MetricReader() {
+ @Override
+ public void register(CollectionRegistration registration) {}
+
+ @Override
+ public CompletableResultCode forceFlush() {
+ return CompletableResultCode.ofSuccess();
+ }
+
+ @Override
+ public CompletableResultCode shutdown() {
+ return CompletableResultCode.ofSuccess();
+ }
+
+ @Override
+ public AggregationTemporality getAggregationTemporality(
+ InstrumentType instrumentType) {
+ return AggregationTemporality.CUMULATIVE;
+ }
+ })
+ .build());
+ ExporterInstrumentation instrumentation =
+ new ExporterInstrumentation(
+ schemaVersion,
+ meterProviderSupplier,
+ ComponentId.generateLazy(StandardComponentId.ExporterType.OTLP_GRPC_SPAN_EXPORTER),
+ "http://testing:1234");
+ verifyNoInteractions(meterProviderSupplier); // Ensure lazy
+
+ // Verify the supplier is only called once per underlying meter.
+
+ instrumentation.startRecordingExport(42).finishFailed("foo");
+ instrumentation.startRecordingExport(42).finishSuccessful();
+ verify(meterProviderSupplier, atLeastOnce()).get();
+
+ instrumentation.startRecordingExport(42).finishFailed("foo");
+ instrumentation.startRecordingExport(42).finishSuccessful();
+ verifyNoMoreInteractions(meterProviderSupplier);
+ }
+
+ @ParameterizedTest
+ @EnumSource
+ void noopMeterProvider(InternalTelemetryVersion schemaVersion) {
+
+ when(meterProviderSupplier.get()).thenReturn(MeterProvider.noop());
+ ExporterInstrumentation instrumentation =
+ new ExporterInstrumentation(
+ schemaVersion,
+ meterProviderSupplier,
+ ComponentId.generateLazy(StandardComponentId.ExporterType.OTLP_GRPC_SPAN_EXPORTER),
+ "http://testing:1234");
+ verifyNoInteractions(meterProviderSupplier); // Ensure lazy
+
+ // Verify the supplier is invoked multiple times since it returns a noop meter.
+ instrumentation.startRecordingExport(42).finishFailed("foo");
+ instrumentation.startRecordingExport(42).finishSuccessful();
+ verify(meterProviderSupplier, atLeastOnce()).get();
+
+ Mockito.clearInvocations((Object) meterProviderSupplier);
+ instrumentation.startRecordingExport(42).finishFailed("foo");
+ instrumentation.startRecordingExport(42).finishSuccessful();
+ verify(meterProviderSupplier, atLeastOnce()).get();
+ }
+
+ @Test
+ void serverAttributesInvalidUrl() {
+ assertThat(ExporterInstrumentation.extractServerAttributes("^")).isEmpty();
+ }
+
+ @Test
+ void serverAttributesEmptyUrl() {
+ assertThat(ExporterInstrumentation.extractServerAttributes("")).isEmpty();
+ }
+
+ @Test
+ void serverAttributesHttps() {
+ assertThat(ExporterInstrumentation.extractServerAttributes("https://example.com/foo/bar?a=b"))
+ .hasSize(2)
+ .containsEntry(SemConvAttributes.SERVER_ADDRESS, "example.com")
+ .containsEntry(SemConvAttributes.SERVER_PORT, 443);
+
+ assertThat(
+ ExporterInstrumentation.extractServerAttributes("https://example.com:1234/foo/bar?a=b"))
+ .hasSize(2)
+ .containsEntry(SemConvAttributes.SERVER_ADDRESS, "example.com")
+ .containsEntry(SemConvAttributes.SERVER_PORT, 1234);
+ }
+
+ @Test
+ void serverAttributesHttp() {
+ assertThat(ExporterInstrumentation.extractServerAttributes("http://example.com/foo/bar?a=b"))
+ .hasSize(2)
+ .containsEntry(SemConvAttributes.SERVER_ADDRESS, "example.com")
+ .containsEntry(SemConvAttributes.SERVER_PORT, 80);
+
+ assertThat(
+ ExporterInstrumentation.extractServerAttributes("http://example.com:1234/foo/bar?a=b"))
+ .hasSize(2)
+ .containsEntry(SemConvAttributes.SERVER_ADDRESS, "example.com")
+ .containsEntry(SemConvAttributes.SERVER_PORT, 1234);
+ }
+
+ @Test
+ void serverAttributesUnknownScheme() {
+ assertThat(ExporterInstrumentation.extractServerAttributes("custom://foo"))
+ .hasSize(1)
+ .containsEntry(SemConvAttributes.SERVER_ADDRESS, "foo");
+
+ assertThat(ExporterInstrumentation.extractServerAttributes("custom://foo:1234"))
+ .hasSize(2)
+ .containsEntry(SemConvAttributes.SERVER_ADDRESS, "foo")
+ .containsEntry(SemConvAttributes.SERVER_PORT, 1234);
+ }
+}
diff --git a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/internal/copied/RateLimiterTest.java b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/internal/copied/RateLimiterTest.java
new file mode 100644
index 00000000000..cace1b8994f
--- /dev/null
+++ b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/internal/copied/RateLimiterTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin.internal.copied;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.opentelemetry.sdk.testing.time.TestClock;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.jupiter.api.Test;
+
+/**
+ * This class was taken from Jaeger java client.
+ * https://github.com/jaegertracing/jaeger-client-java/blob/master/jaeger-core/src/test/java/io/jaegertracing/internal/utils/RateLimiterTest.java
+ */
+class RateLimiterTest {
+
+ @Test
+ void testRateLimiterWholeNumber() {
+ TestClock clock = TestClock.create();
+ RateLimiter limiter = new RateLimiter(2.0, 2.0, clock);
+
+ assertThat(limiter.trySpend(1.0)).isTrue();
+ assertThat(limiter.trySpend(1.0)).isTrue();
+ assertThat(limiter.trySpend(1.0)).isFalse();
+ // move time 250ms forward, not enough credits to pay for 1.0 item
+ clock.advance(Duration.ofNanos(TimeUnit.MILLISECONDS.toNanos(250)));
+ assertThat(limiter.trySpend(1.0)).isFalse();
+
+ // move time 500ms forward, now enough credits to pay for 1.0 item
+ clock.advance(Duration.ofNanos(TimeUnit.MILLISECONDS.toNanos(500)));
+
+ assertThat(limiter.trySpend(1.0)).isTrue();
+ assertThat(limiter.trySpend(1.0)).isFalse();
+
+ // move time 5s forward, enough to accumulate credits for 10 messages, but it should still be
+ // capped at 2
+ clock.advance(Duration.ofNanos(TimeUnit.MILLISECONDS.toNanos(5000)));
+
+ assertThat(limiter.trySpend(1.0)).isTrue();
+ assertThat(limiter.trySpend(1.0)).isTrue();
+ assertThat(limiter.trySpend(1.0)).isFalse();
+ assertThat(limiter.trySpend(1.0)).isFalse();
+ assertThat(limiter.trySpend(1.0)).isFalse();
+ }
+
+ @Test
+ void testRateLimiterSteadyRate() {
+ TestClock clock = TestClock.create();
+ RateLimiter limiter = new RateLimiter(5.0 / 60.0, 5.0, clock);
+ for (int i = 0; i < 100; i++) {
+ assertThat(limiter.trySpend(1.0)).isTrue();
+ clock.advance(Duration.ofNanos(TimeUnit.SECONDS.toNanos(20)));
+ }
+ }
+
+ @Test
+ void cantWithdrawMoreThanMax() {
+ TestClock clock = TestClock.create();
+ RateLimiter limiter = new RateLimiter(1, 1.0, clock);
+ assertThat(limiter.trySpend(2)).isFalse();
+ }
+
+ @Test
+ void testRateLimiterLessThanOne() {
+ TestClock clock = TestClock.create();
+ RateLimiter limiter = new RateLimiter(0.5, 0.5, clock);
+
+ assertThat(limiter.trySpend(0.25)).isTrue();
+ assertThat(limiter.trySpend(0.25)).isTrue();
+ assertThat(limiter.trySpend(0.25)).isFalse();
+ // move time 250ms forward, not enough credits to pay for 1.0 item
+ clock.advance(Duration.ofNanos(TimeUnit.MILLISECONDS.toNanos(250)));
+ assertThat(limiter.trySpend(0.25)).isFalse();
+
+ // move time 500ms forward, now enough credits to pay for 1.0 item
+ clock.advance(Duration.ofNanos(TimeUnit.MILLISECONDS.toNanos(500)));
+
+ assertThat(limiter.trySpend(0.25)).isTrue();
+ assertThat(limiter.trySpend(0.25)).isFalse();
+
+ // move time 5s forward, enough to accumulate credits for 10 messages, but it should still be
+ // capped at 2
+ clock.advance(Duration.ofNanos(TimeUnit.MILLISECONDS.toNanos(5000)));
+
+ assertThat(limiter.trySpend(0.25)).isTrue();
+ assertThat(limiter.trySpend(0.25)).isTrue();
+ assertThat(limiter.trySpend(0.25)).isFalse();
+ assertThat(limiter.trySpend(0.25)).isFalse();
+ assertThat(limiter.trySpend(0.25)).isFalse();
+ }
+
+ @Test
+ void testRateLimiterMaxBalance() {
+ TestClock clock = TestClock.create();
+ RateLimiter limiter = new RateLimiter(0.1, 1.0, clock);
+
+ clock.advance(Duration.ofNanos(TimeUnit.MICROSECONDS.toNanos(100)));
+ assertThat(limiter.trySpend(1.0)).isTrue();
+ assertThat(limiter.trySpend(1.0)).isFalse();
+
+ // move time 20s forward, enough to accumulate credits for 2 messages, but it should still be
+ // capped at 1
+ clock.advance(Duration.ofNanos(TimeUnit.MILLISECONDS.toNanos(20000)));
+
+ assertThat(limiter.trySpend(1.0)).isTrue();
+ assertThat(limiter.trySpend(1.0)).isFalse();
+ }
+
+ /**
+ * Validates rate limiter behavior with {@link System#nanoTime()}-like (non-zero) initial nano
+ * ticks.
+ */
+ @Test
+ void testRateLimiterInitial() {
+ TestClock clock = TestClock.create();
+ RateLimiter limiter = new RateLimiter(1000, 100, clock);
+
+ assertThat(limiter.trySpend(100)).isTrue(); // consume initial (max) balance
+ assertThat(limiter.trySpend(1)).isFalse();
+
+ // add 49 credits
+ clock.advance(Duration.ofNanos(TimeUnit.MILLISECONDS.toNanos(49)));
+ assertThat(limiter.trySpend(50)).isFalse();
+
+ // add one credit
+ clock.advance(Duration.ofNanos(TimeUnit.MILLISECONDS.toNanos(1)));
+ assertThat(limiter.trySpend(50)).isTrue(); // consume accrued balance
+ assertThat(limiter.trySpend(1)).isFalse();
+
+ // add a lot of credits (max out balance)
+ clock.advance(Duration.ofNanos(TimeUnit.MILLISECONDS.toNanos(1_000_000)));
+ assertThat(limiter.trySpend(1)).isTrue(); // take one credit
+
+ // add a lot of credits (max out balance)
+ clock.advance(Duration.ofNanos(TimeUnit.MILLISECONDS.toNanos(1_000_000)));
+ assertThat(limiter.trySpend(101)).isFalse(); // can't consume more than max balance
+ assertThat(limiter.trySpend(100)).isTrue(); // consume max balance
+ assertThat(limiter.trySpend(1)).isFalse();
+ }
+
+ /** Validates concurrent credit check correctness. */
+ @Test
+ void testRateLimiterConcurrency() throws InterruptedException, ExecutionException {
+ int numWorkers = 8;
+ ExecutorService executorService = Executors.newFixedThreadPool(numWorkers);
+ int creditsPerWorker = 1000;
+ TestClock clock = TestClock.create();
+ RateLimiter limiter = new RateLimiter(1, numWorkers * creditsPerWorker, clock);
+ AtomicInteger count = new AtomicInteger();
+ List> futures = new ArrayList<>(numWorkers);
+ for (int w = 0; w < numWorkers; ++w) {
+ Future> future =
+ executorService.submit(
+ () -> {
+ for (int i = 0; i < creditsPerWorker * 2; ++i) {
+ if (limiter.trySpend(1)) {
+ count.getAndIncrement(); // count allowed operations
+ }
+ }
+ });
+ futures.add(future);
+ }
+ for (Future> future : futures) {
+ future.get();
+ }
+ executorService.shutdown();
+ executorService.awaitTermination(1, TimeUnit.SECONDS);
+ assertThat(count.get())
+ .withFailMessage("Exactly the allocated number of credits must be consumed")
+ .isEqualTo(numWorkers * creditsPerWorker);
+ assertThat(limiter.trySpend(1)).isFalse();
+ }
+}
diff --git a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/internal/copied/ThrottlingLoggerTest.java b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/internal/copied/ThrottlingLoggerTest.java
new file mode 100644
index 00000000000..507ed9be7c0
--- /dev/null
+++ b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/internal/copied/ThrottlingLoggerTest.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin.internal.copied;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.slf4j.event.Level.ERROR;
+import static org.slf4j.event.Level.INFO;
+import static org.slf4j.event.Level.WARN;
+
+import io.github.netmikey.logunit.api.LogCapturer;
+import io.opentelemetry.internal.testing.slf4j.SuppressLogger;
+import io.opentelemetry.sdk.common.Clock;
+import io.opentelemetry.sdk.testing.time.TestClock;
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+@SuppressLogger(ThrottlingLoggerTest.class)
+class ThrottlingLoggerTest {
+
+ private static final Logger realLogger = Logger.getLogger(ThrottlingLoggerTest.class.getName());
+
+ @RegisterExtension
+ LogCapturer logs = LogCapturer.create().captureForType(ThrottlingLoggerTest.class);
+
+ @Test
+ void delegation() {
+ ThrottlingLogger logger = new ThrottlingLogger(realLogger);
+
+ logger.log(Level.WARNING, "oh no!");
+ logger.log(Level.INFO, "oh yes!");
+ RuntimeException throwable = new RuntimeException();
+ logger.log(Level.SEVERE, "secrets", throwable);
+
+ logs.assertContains(loggingEvent -> loggingEvent.getLevel().equals(WARN), "oh no!");
+ logs.assertContains(loggingEvent -> loggingEvent.getLevel().equals(INFO), "oh yes!");
+ assertThat(
+ logs.assertContains(loggingEvent -> loggingEvent.getLevel().equals(ERROR), "secrets")
+ .getThrowable())
+ .isSameAs(throwable);
+ }
+
+ @Test
+ void delegationCustom() {
+ ThrottlingLogger logger = new ThrottlingLogger(realLogger, 10, 2, TimeUnit.HOURS);
+
+ logger.log(Level.WARNING, "oh no!");
+ logger.log(Level.INFO, "oh yes!");
+ RuntimeException throwable = new RuntimeException();
+ logger.log(Level.SEVERE, "secrets", throwable);
+
+ logs.assertContains(loggingEvent -> loggingEvent.getLevel().equals(WARN), "oh no!");
+ logs.assertContains(loggingEvent -> loggingEvent.getLevel().equals(INFO), "oh yes!");
+ assertThat(
+ logs.assertContains(loggingEvent -> loggingEvent.getLevel().equals(ERROR), "secrets")
+ .getThrowable())
+ .isSameAs(throwable);
+ }
+
+ @Test
+ void logsBelowLevelDontCount() {
+ ThrottlingLogger logger =
+ new ThrottlingLogger(Logger.getLogger(ThrottlingLoggerTest.class.getName()));
+
+ for (int i = 0; i < 100; i++) {
+ // FINE is below the default level and thus shouldn't impact the rate.
+ logger.log(Level.FINE, "secrets", new RuntimeException());
+ }
+ logger.log(Level.INFO, "oh yes!");
+
+ logs.assertContains(loggingEvent -> loggingEvent.getLevel().equals(INFO), "oh yes!");
+ }
+
+ @Test
+ void fiveInAMinuteTriggersLimiting() {
+ Clock clock = TestClock.create();
+ ThrottlingLogger logger = new ThrottlingLogger(realLogger, clock);
+
+ logger.log(Level.WARNING, "oh no!");
+ logger.log(Level.WARNING, "oh no!");
+ logger.log(Level.WARNING, "oh no!");
+ logger.log(Level.WARNING, "oh no!");
+ logger.log(Level.WARNING, "oh no!");
+
+ logger.log(Level.WARNING, "oh no I should trigger suppression!");
+ logger.log(Level.WARNING, "oh no I should be suppressed!");
+
+ assertThat(logs.getEvents()).hasSize(7);
+ logs.assertDoesNotContain("oh no I should be suppressed!");
+ logs.assertContains(
+ "Too many log messages detected. Will only log 1 time(s) per minute from now on.");
+ logs.assertContains("oh no I should trigger suppression!");
+ }
+
+ @Test
+ void tenInAnHourTriggersLimiting() {
+ Clock clock = TestClock.create();
+ ThrottlingLogger logger = new ThrottlingLogger(realLogger, clock, 10, 2, TimeUnit.HOURS);
+
+ for (int i = 0; i < 10; i++) {
+ logger.log(Level.WARNING, "oh no!");
+ }
+
+ logger.log(Level.WARNING, "oh no I should trigger suppression!");
+ logger.log(Level.WARNING, "oh no I should be suppressed!");
+
+ assertThat(logs.getEvents()).hasSize(12);
+ logs.assertDoesNotContain("oh no I should be suppressed!");
+ logs.assertContains(
+ "Too many log messages detected. Will only log 2 time(s) per hour from now on.");
+ logs.assertContains("oh no I should trigger suppression!");
+ }
+
+ @Test
+ void allowsTrickleOfMessages() {
+ TestClock clock = TestClock.create();
+ ThrottlingLogger logger = new ThrottlingLogger(realLogger, clock);
+ logger.log(Level.WARNING, "oh no!");
+ assertThat(logs.size()).isEqualTo(1);
+ logger.log(Level.WARNING, "oh no!");
+ assertThat(logs.size()).isEqualTo(2);
+ clock.advance(Duration.ofMillis(30_001));
+ logger.log(Level.WARNING, "oh no!");
+ logger.log(Level.WARNING, "oh no!");
+ assertThat(logs.size()).isEqualTo(4);
+
+ clock.advance(Duration.ofMillis(30_001));
+ logger.log(Level.WARNING, "oh no 2nd minute!");
+ logger.log(Level.WARNING, "oh no 2nd minute!");
+ assertThat(logs.size()).isEqualTo(6);
+ clock.advance(Duration.ofMillis(30_001));
+ logger.log(Level.WARNING, "oh no 2nd minute!");
+ logger.log(Level.WARNING, "oh no 2nd minute!");
+ assertThat(logs.size()).isEqualTo(8);
+
+ clock.advance(Duration.ofMillis(30_001));
+ logger.log(Level.WARNING, "oh no 3rd minute!");
+ logger.log(Level.WARNING, "oh no 3rd minute!");
+ assertThat(logs.size()).isEqualTo(10);
+ clock.advance(Duration.ofMillis(30_001));
+ logger.log(Level.WARNING, "oh no 3rd minute!");
+ logger.log(Level.WARNING, "oh no 3rd minute!");
+ assertThat(logs.size()).isEqualTo(12);
+ }
+
+ @Test
+ void afterAMinuteLetOneThrough() {
+ TestClock clock = TestClock.create();
+ ThrottlingLogger logger = new ThrottlingLogger(realLogger, clock);
+
+ logger.log(Level.WARNING, "oh no!");
+ logger.log(Level.WARNING, "oh no!");
+ logger.log(Level.WARNING, "oh no!");
+ logger.log(Level.WARNING, "oh no!");
+ logger.log(Level.WARNING, "oh no!");
+
+ logger.log(Level.WARNING, "oh no I should trigger suppression!");
+ logger.log(Level.WARNING, "oh no I should be suppressed!");
+
+ assertThat(logs.getEvents()).hasSize(7);
+ logs.assertDoesNotContain("oh no I should be suppressed!");
+ logs.assertContains("oh no I should trigger suppression!");
+ logs.assertContains(
+ "Too many log messages detected. Will only log 1 time(s) per minute from now on.");
+
+ clock.advance(Duration.ofMillis(60_001));
+ logger.log(Level.WARNING, "oh no!");
+ logger.log(Level.WARNING, "oh no I should be suppressed!");
+ assertThat(logs.getEvents()).hasSize(8);
+ assertThat(logs.getEvents().get(7).getMessage()).isEqualTo("oh no!");
+
+ clock.advance(Duration.ofMillis(60_001));
+ logger.log(Level.WARNING, "oh no!");
+ logger.log(Level.WARNING, "oh no I should be suppressed!");
+ assertThat(logs.getEvents()).hasSize(9);
+ assertThat(logs.getEvents().get(8).getMessage()).isEqualTo("oh no!");
+ }
+
+ @Test
+ void afterAnHourLetTwoThrough() {
+ TestClock clock = TestClock.create();
+ ThrottlingLogger logger = new ThrottlingLogger(realLogger, clock, 10, 2, TimeUnit.HOURS);
+
+ for (int i = 0; i < 10; i++) {
+ logger.log(Level.WARNING, "oh no!");
+ }
+
+ logger.log(Level.WARNING, "oh no I should trigger suppression!");
+ logger.log(Level.WARNING, "oh no I should be suppressed!");
+
+ assertThat(logs.getEvents()).hasSize(12);
+ logs.assertDoesNotContain("oh no I should be suppressed!");
+ logs.assertContains("oh no I should trigger suppression!");
+ logs.assertContains(
+ "Too many log messages detected. Will only log 2 time(s) per hour from now on.");
+
+ clock.advance(Duration.ofMinutes(61));
+ logger.log(Level.WARNING, "oh no!");
+ logger.log(Level.WARNING, "oh no!");
+ logger.log(Level.WARNING, "oh no I should be suppressed!");
+ assertThat(logs.getEvents()).hasSize(14);
+ assertThat(logs.getEvents().get(13).getMessage()).isEqualTo("oh no!");
+
+ clock.advance(Duration.ofMinutes(61));
+ logger.log(Level.WARNING, "oh no!");
+ logger.log(Level.WARNING, "oh no!");
+ logger.log(Level.WARNING, "oh no I should be suppressed!");
+ assertThat(logs.getEvents()).hasSize(16);
+ assertThat(logs.getEvents().get(15).getMessage()).isEqualTo("oh no!");
+
+ logs.assertDoesNotContain("oh no I should be suppressed!");
+ }
+
+ @Test
+ void allowOnlyOneLogPerMinuteAfterSuppression() {
+ TestClock clock = TestClock.create();
+ ThrottlingLogger logger = new ThrottlingLogger(realLogger, clock);
+
+ logger.log(Level.WARNING, "oh no!");
+ logger.log(Level.WARNING, "oh no!");
+ logger.log(Level.WARNING, "oh no!");
+ logger.log(Level.WARNING, "oh no!");
+ logger.log(Level.WARNING, "oh no!");
+
+ logger.log(Level.WARNING, "oh no I should trigger suppression!");
+ logger.log(Level.WARNING, "oh no I should be suppressed!");
+
+ assertThat(logs.getEvents()).hasSize(7);
+
+ clock.advance(Duration.ofMillis(12_001));
+ logger.log(Level.WARNING, "suppression 1");
+ clock.advance(Duration.ofMillis(12_001));
+ logger.log(Level.WARNING, "suppression 2");
+ clock.advance(Duration.ofMillis(12_001));
+ logger.log(Level.WARNING, "suppression 3");
+ clock.advance(Duration.ofMillis(12_001));
+ logger.log(Level.WARNING, "suppression 4");
+ clock.advance(Duration.ofMillis(12_001));
+ logger.log(Level.WARNING, "allowed 1");
+
+ logs.assertDoesNotContain("suppression 1");
+ logs.assertDoesNotContain("suppression 2");
+ logs.assertDoesNotContain("suppression 3");
+ logs.assertDoesNotContain("suppression 4");
+ logs.assertContains("allowed 1");
+
+ assertThat(logs.getEvents()).hasSize(8);
+ assertThat(logs.getEvents().get(7).getMessage()).isEqualTo("allowed 1");
+ }
+
+ @Test
+ void allowOnlyTwoLogPerHourAfterSuppression() {
+ TestClock clock = TestClock.create();
+ ThrottlingLogger logger = new ThrottlingLogger(realLogger, clock, 10, 2, TimeUnit.HOURS);
+
+ for (int i = 0; i < 10; i++) {
+ logger.log(Level.WARNING, "oh no!");
+ }
+
+ logger.log(Level.WARNING, "oh no I should trigger suppression!");
+ logger.log(Level.WARNING, "oh no I should be suppressed!");
+
+ assertThat(logs.getEvents()).hasSize(12);
+
+ clock.advance(Duration.ofMinutes(10));
+ logger.log(Level.WARNING, "suppression 1");
+ clock.advance(Duration.ofMinutes(10));
+ logger.log(Level.WARNING, "suppression 2");
+ clock.advance(Duration.ofMinutes(10));
+ clock.advance(Duration.ofSeconds(1));
+ logger.log(Level.WARNING, "allowed 1");
+ clock.advance(Duration.ofMinutes(10));
+ logger.log(Level.WARNING, "suppression 3");
+ clock.advance(Duration.ofMinutes(10));
+ logger.log(Level.WARNING, "suppression 4");
+ clock.advance(Duration.ofMinutes(10));
+ clock.advance(Duration.ofSeconds(1));
+ logger.log(Level.WARNING, "allowed 2");
+
+ logs.assertDoesNotContain("suppression 1");
+ logs.assertDoesNotContain("suppression 2");
+ logs.assertDoesNotContain("suppression 3");
+ logs.assertDoesNotContain("suppression 4");
+ logs.assertContains("allowed 1");
+ logs.assertContains("allowed 2");
+
+ assertThat(logs.getEvents()).hasSize(14);
+ assertThat(logs.getEvents().get(12).getMessage()).isEqualTo("allowed 1");
+ assertThat(logs.getEvents().get(13).getMessage()).isEqualTo("allowed 2");
+ }
+}
diff --git a/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/ConfigurableSpanExporterTest.java b/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/ConfigurableSpanExporterTest.java
index d2c66386092..69a86641084 100644
--- a/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/ConfigurableSpanExporterTest.java
+++ b/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/ConfigurableSpanExporterTest.java
@@ -13,7 +13,6 @@
import io.opentelemetry.exporter.logging.LoggingSpanExporter;
import io.opentelemetry.exporter.otlp.internal.OtlpSpanExporterProvider;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
-import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter;
import io.opentelemetry.internal.testing.CleanupExtension;
import io.opentelemetry.sdk.autoconfigure.internal.NamedSpiManager;
import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper;
@@ -37,6 +36,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
+@SuppressWarnings("deprecation") // testing deprecated code
class ConfigurableSpanExporterTest {
@RegisterExtension CleanupExtension cleanup = new CleanupExtension();
@@ -168,7 +168,9 @@ void configureSpanProcessors_batchSpanProcessor() {
TracerProviderConfiguration.configureSpanProcessors(
DefaultConfigProperties.createFromMap(
Collections.singletonMap("otel.traces.exporter", exporterName)),
- ImmutableMap.of(exporterName, ZipkinSpanExporter.builder().build()),
+ ImmutableMap.of(
+ exporterName,
+ io.opentelemetry.exporter.zipkin.ZipkinSpanExporter.builder().build()),
MeterProvider.noop(),
closeables);
cleanup.addCloseables(closeables);
@@ -189,7 +191,7 @@ void configureSpanProcessors_multipleExporters() {
"otlp",
OtlpGrpcSpanExporter.builder().build(),
"zipkin",
- ZipkinSpanExporter.builder().build()),
+ io.opentelemetry.exporter.zipkin.ZipkinSpanExporter.builder().build()),
MeterProvider.noop(),
closeables);
cleanup.addCloseables(closeables);
@@ -212,7 +214,8 @@ void configureSpanProcessors_multipleExporters() {
spanExporters -> {
assertThat(spanExporters.length).isEqualTo(2);
assertThat(spanExporters)
- .hasAtLeastOneElementOfType(ZipkinSpanExporter.class)
+ .hasAtLeastOneElementOfType(
+ io.opentelemetry.exporter.zipkin.ZipkinSpanExporter.class)
.hasAtLeastOneElementOfType(OtlpGrpcSpanExporter.class);
});
});
@@ -231,7 +234,7 @@ void configureSpanProcessors_multipleExportersWithLogging() {
"logging",
LoggingSpanExporter.create(),
"zipkin",
- ZipkinSpanExporter.builder().build()),
+ io.opentelemetry.exporter.zipkin.ZipkinSpanExporter.builder().build()),
MeterProvider.noop(),
closeables);
cleanup.addCloseables(closeables);
diff --git a/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/SpanExporterConfigurationTest.java b/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/SpanExporterConfigurationTest.java
index 64e4a55e91a..4d94edb48cc 100644
--- a/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/SpanExporterConfigurationTest.java
+++ b/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/SpanExporterConfigurationTest.java
@@ -12,7 +12,6 @@
import io.opentelemetry.exporter.logging.LoggingSpanExporter;
import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingSpanExporter;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
-import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter;
import io.opentelemetry.sdk.autoconfigure.internal.NamedSpiManager;
import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
@@ -22,6 +21,7 @@
import java.util.Collections;
import org.junit.jupiter.api.Test;
+@SuppressWarnings("deprecation") // testing deprecated code
class SpanExporterConfigurationTest {
private final SpiHelper spiHelper =
@@ -42,7 +42,7 @@ void configureExporter_KnownSpiExportersOnClasspath() {
assertThat(SpanExporterConfiguration.configureExporter("otlp", spiExportersManager))
.isInstanceOf(OtlpGrpcSpanExporter.class);
assertThat(SpanExporterConfiguration.configureExporter("zipkin", spiExportersManager))
- .isInstanceOf(ZipkinSpanExporter.class);
+ .isInstanceOf(io.opentelemetry.exporter.zipkin.ZipkinSpanExporter.class);
}
@Test