diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/http/HttpClientExperimentalMetrics.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/http/HttpClientExperimentalMetrics.java index 4169fe270d78..b3e94d89f065 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/http/HttpClientExperimentalMetrics.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/http/HttpClientExperimentalMetrics.java @@ -17,6 +17,7 @@ import io.opentelemetry.context.ContextKey; import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; +import io.opentelemetry.instrumentation.api.internal.OperationMetricsUtil; import java.util.logging.Logger; /** @@ -42,7 +43,8 @@ public final class HttpClientExperimentalMetrics implements OperationListener { * io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder}. */ public static OperationMetrics get() { - return HttpClientExperimentalMetrics::new; + return OperationMetricsUtil.create( + "experimental http client", HttpClientExperimentalMetrics::new); } private final LongHistogram requestSize; diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/http/HttpServerExperimentalMetrics.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/http/HttpServerExperimentalMetrics.java index 3c67827e0315..9459293ae1ab 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/http/HttpServerExperimentalMetrics.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/http/HttpServerExperimentalMetrics.java @@ -19,6 +19,7 @@ import io.opentelemetry.context.ContextKey; import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; +import io.opentelemetry.instrumentation.api.internal.OperationMetricsUtil; import java.util.logging.Logger; /** @@ -46,7 +47,8 @@ public final class HttpServerExperimentalMetrics implements OperationListener { * io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder}. */ public static OperationMetrics get() { - return HttpServerExperimentalMetrics::new; + return OperationMetricsUtil.create( + "experimental http server", HttpServerExperimentalMetrics::new); } private final LongUpDownCounter activeRequests; diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetrics.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetrics.java index 918c16fafa93..72244fe698f4 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetrics.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetrics.java @@ -16,6 +16,7 @@ import io.opentelemetry.context.ContextKey; import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; +import io.opentelemetry.instrumentation.api.internal.OperationMetricsUtil; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; @@ -51,7 +52,7 @@ private RpcClientMetrics(Meter meter) { * io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder}. */ public static OperationMetrics get() { - return RpcClientMetrics::new; + return OperationMetricsUtil.create("rpc client", RpcClientMetrics::new); } @Override diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetrics.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetrics.java index ea04736a3aab..c4ca4265d793 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetrics.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetrics.java @@ -16,6 +16,7 @@ import io.opentelemetry.context.ContextKey; import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; +import io.opentelemetry.instrumentation.api.internal.OperationMetricsUtil; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; @@ -51,7 +52,7 @@ private RpcServerMetrics(Meter meter) { * io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder}. */ public static OperationMetrics get() { - return RpcServerMetrics::new; + return OperationMetricsUtil.create("rpc server", RpcServerMetrics::new); } @Override diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/OperationMetricsUtil.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/OperationMetricsUtil.java new file mode 100644 index 000000000000..6826691c530d --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/OperationMetricsUtil.java @@ -0,0 +1,72 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.internal; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.DoubleHistogramBuilder; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.context.Context; +import io.opentelemetry.extension.incubator.metrics.ExtendedDoubleHistogramBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; +import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public class OperationMetricsUtil { + private static final Logger logger = Logger.getLogger(OperationMetricsUtil.class.getName()); + private static final OperationListener NOOP_OPERATION_LISTENER = + new OperationListener() { + + @Override + public Context onStart(Context context, Attributes startAttributes, long startNanos) { + return context; + } + + @Override + public void onEnd(Context context, Attributes endAttributes, long endNanos) {} + }; + + public static OperationMetrics create( + String description, Function factory) { + return create( + description, + factory, + (s, histogramBuilder) -> + logger.log( + Level.WARNING, + "Disabling {0} metrics because {1} does not implement {2}. This prevents using " + + "metrics advice, which could result in {0} having high cardinality attributes.", + new Object[] { + description, + histogramBuilder.getClass().getName(), + ExtendedDoubleHistogramBuilder.class.getName() + })); + } + + // visible for testing + static OperationMetrics create( + String description, + Function factory, + BiConsumer warningEmitter) { + return meter -> { + DoubleHistogramBuilder histogramBuilder = meter.histogramBuilder("compatibility-test"); + if (!(histogramBuilder instanceof ExtendedDoubleHistogramBuilder) + && !histogramBuilder.getClass().getName().contains("NoopDoubleHistogram")) { + warningEmitter.accept(description, histogramBuilder); + return NOOP_OPERATION_LISTENER; + } + return factory.apply(meter); + }; + } + + private OperationMetricsUtil() {} +} diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/semconv/http/HttpClientMetrics.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/semconv/http/HttpClientMetrics.java index 1d007a10b462..6f9974206e3d 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/semconv/http/HttpClientMetrics.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/semconv/http/HttpClientMetrics.java @@ -17,6 +17,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; +import io.opentelemetry.instrumentation.api.internal.OperationMetricsUtil; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; @@ -43,7 +44,7 @@ public final class HttpClientMetrics implements OperationListener { * @see InstrumenterBuilder#addOperationMetrics(OperationMetrics) */ public static OperationMetrics get() { - return HttpClientMetrics::new; + return OperationMetricsUtil.create("http client", HttpClientMetrics::new); } private final DoubleHistogram duration; diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/semconv/http/HttpServerMetrics.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/semconv/http/HttpServerMetrics.java index cc6ad24d3ae0..58cd3abf5caf 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/semconv/http/HttpServerMetrics.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/semconv/http/HttpServerMetrics.java @@ -17,6 +17,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; +import io.opentelemetry.instrumentation.api.internal.OperationMetricsUtil; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; @@ -43,7 +44,7 @@ public final class HttpServerMetrics implements OperationListener { * @see InstrumenterBuilder#addOperationMetrics(OperationMetrics) */ public static OperationMetrics get() { - return HttpServerMetrics::new; + return OperationMetricsUtil.create("http server", HttpServerMetrics::new); } private final DoubleHistogram duration; diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/internal/OperationMetricsUtilTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/internal/OperationMetricsUtilTest.java new file mode 100644 index 000000000000..945ab3297a1e --- /dev/null +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/internal/OperationMetricsUtilTest.java @@ -0,0 +1,78 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.internal; + +import io.opentelemetry.api.metrics.DoubleHistogramBuilder; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.MeterProvider; +import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import java.lang.reflect.Proxy; +import java.util.concurrent.atomic.AtomicBoolean; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +class OperationMetricsUtilTest { + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @Test + void noWarning() { + AtomicBoolean warning = new AtomicBoolean(false); + OperationMetrics operationMetrics = + OperationMetricsUtil.create( + "test metrics", meter -> null, (s, doubleHistogramBuilder) -> warning.set(true)); + operationMetrics.create(testing.getOpenTelemetry().getMeter("test")); + + Assertions.assertThat(warning).isFalse(); + } + + @Test + void noWarningWithNoopMetrics() { + AtomicBoolean warning = new AtomicBoolean(false); + OperationMetrics operationMetrics = + OperationMetricsUtil.create( + "test metrics", meter -> null, (s, doubleHistogramBuilder) -> warning.set(true)); + operationMetrics.create(MeterProvider.noop().get("test")); + + Assertions.assertThat(warning).isFalse(); + } + + @Test + void warning() { + AtomicBoolean warning = new AtomicBoolean(false); + OperationMetrics operationMetrics = + OperationMetricsUtil.create( + "test metrics", meter -> null, (s, doubleHistogramBuilder) -> warning.set(true)); + Meter defaultMeter = MeterProvider.noop().get("test"); + Meter meter = + (Meter) + Proxy.newProxyInstance( + Meter.class.getClassLoader(), + new Class[] {Meter.class}, + (proxy, method, args) -> { + if ("histogramBuilder".equals(method.getName())) { + // proxy the histogram builder so that the builder instance does not implement + // ExtendedDoubleHistogramBuilder which will trigger the warning + return proxyDoubleHistogramBuilder(defaultMeter); + } + return method.invoke(defaultMeter, args); + }); + operationMetrics.create(meter); + + Assertions.assertThat(warning).isTrue(); + } + + private static DoubleHistogramBuilder proxyDoubleHistogramBuilder(Meter meter) { + return (DoubleHistogramBuilder) + Proxy.newProxyInstance( + DoubleHistogramBuilder.class.getClassLoader(), + new Class[] {DoubleHistogramBuilder.class}, + (proxy1, method1, args1) -> meter.histogramBuilder((String) args1[0])); + } +}