diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpDeclarativeConfigUtil.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpDeclarativeConfigUtil.java index ef4e2b8a099..141f39ec4fb 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpDeclarativeConfigUtil.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpDeclarativeConfigUtil.java @@ -10,20 +10,26 @@ import static io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil.readFileBytes; import static io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil.validateEndpoint; +import io.opentelemetry.api.incubator.config.ConfigProvider; +import io.opentelemetry.api.incubator.config.DeclarativeConfigException; import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; import io.opentelemetry.common.ComponentLoader; import io.opentelemetry.exporter.internal.IncubatingExporterBuilderUtil; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ExtendedDeclarativeConfigProperties; +import io.opentelemetry.sdk.common.InternalTelemetryVersion; import io.opentelemetry.sdk.common.export.MemoryMode; import io.opentelemetry.sdk.common.export.RetryPolicy; import java.net.URL; import java.time.Duration; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.function.BiConsumer; import java.util.function.Consumer; +import javax.annotation.Nullable; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at @@ -47,7 +53,7 @@ public static String getStructuredConfigOtlpProtocol(DeclarativeConfigProperties @SuppressWarnings("TooManyParameters") public static void configureOtlpExporterBuilder( String dataType, - DeclarativeConfigProperties config, + DeclarativeConfigProperties baseConfig, Consumer setComponentLoader, Consumer setEndpoint, BiConsumer addHeader, @@ -57,7 +63,14 @@ public static void configureOtlpExporterBuilder( BiConsumer setClientTls, Consumer setRetryPolicy, Consumer setMemoryMode, - boolean isHttpProtobuf) { + boolean isHttpProtobuf, + Consumer internalTelemetryVersionConsumer, + Runnable setNoopMeterProvider) { + if (!(baseConfig instanceof ExtendedDeclarativeConfigProperties)) { + throw new IllegalArgumentException("Expected ExtendedDeclarativeConfigProperties"); + } + ExtendedDeclarativeConfigProperties config = (ExtendedDeclarativeConfigProperties) baseConfig; + setComponentLoader.accept(config.getComponentLoader()); URL endpoint = validateEndpoint(config.getString("endpoint"), isHttpProtobuf); @@ -119,6 +132,35 @@ public static void configureOtlpExporterBuilder( } IncubatingExporterBuilderUtil.configureExporterMemoryMode(config, setMemoryMode); + + // InternalTelemetryVersion defaults to disabled (i.e. null) until semantic conventions are + // stable. To disable, set a noop meter provider. + InternalTelemetryVersion telemetryVersion = + getInternalTelemetryVersion(config.getConfigProvider()); + if (telemetryVersion == null) { + setNoopMeterProvider.run(); + } else { + internalTelemetryVersionConsumer.accept(telemetryVersion); + } + } + + @Nullable + private static InternalTelemetryVersion getInternalTelemetryVersion( + ConfigProvider configProvider) { + String internalTelemetryVersion = + configProvider.getInstrumentationConfig("otel_sdk").getString("internal_telemetry_version"); + if (internalTelemetryVersion == null) { + return null; + } + switch (internalTelemetryVersion.toLowerCase(Locale.ROOT)) { + case "legacy": + return InternalTelemetryVersion.LEGACY; + case "latest": + return InternalTelemetryVersion.LATEST; + default: + throw new DeclarativeConfigException( + "Invalid sdk telemetry version: " + internalTelemetryVersion); + } } private OtlpDeclarativeConfigUtil() {} diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpGrpcLogRecordExporterComponentProvider.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpGrpcLogRecordExporterComponentProvider.java index 6afb9a601cd..29382dd70c3 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpGrpcLogRecordExporterComponentProvider.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpGrpcLogRecordExporterComponentProvider.java @@ -8,6 +8,7 @@ import static io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil.DATA_TYPE_LOGS; import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter; import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporterBuilder; import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; @@ -47,7 +48,9 @@ public LogRecordExporter create(DeclarativeConfigProperties config) { builder::setClientTls, builder::setRetryPolicy, builder::setMemoryMode, - /* isHttpProtobuf= */ false); + /* isHttpProtobuf= */ false, + builder::setInternalTelemetryVersion, + () -> builder.setMeterProvider(MeterProvider::noop)); return builder.build(); } diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpGrpcMetricExporterComponentProvider.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpGrpcMetricExporterComponentProvider.java index cf8107f8499..ca043ecc8de 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpGrpcMetricExporterComponentProvider.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpGrpcMetricExporterComponentProvider.java @@ -8,6 +8,7 @@ import static io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil.DATA_TYPE_METRICS; import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.exporter.internal.IncubatingExporterBuilderUtil; import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder; @@ -48,7 +49,9 @@ public MetricExporter create(DeclarativeConfigProperties config) { builder::setClientTls, builder::setRetryPolicy, builder::setMemoryMode, - /* isHttpProtobuf= */ false); + /* isHttpProtobuf= */ false, + builder::setInternalTelemetryVersion, + () -> builder.setMeterProvider(MeterProvider::noop)); IncubatingExporterBuilderUtil.configureOtlpAggregationTemporality( config, builder::setAggregationTemporalitySelector); IncubatingExporterBuilderUtil.configureOtlpHistogramDefaultAggregation( diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpGrpcSpanExporterComponentProvider.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpGrpcSpanExporterComponentProvider.java index 17995efb946..ce0c97978db 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpGrpcSpanExporterComponentProvider.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpGrpcSpanExporterComponentProvider.java @@ -8,6 +8,7 @@ import static io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil.DATA_TYPE_TRACES; import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder; import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; @@ -47,7 +48,9 @@ public SpanExporter create(DeclarativeConfigProperties config) { builder::setClientTls, builder::setRetryPolicy, builder::setMemoryMode, - /* isHttpProtobuf= */ false); + /* isHttpProtobuf= */ false, + builder::setInternalTelemetryVersion, + () -> builder.setMeterProvider(MeterProvider::noop)); return builder.build(); } diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpHttpLogRecordExporterComponentProvider.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpHttpLogRecordExporterComponentProvider.java index a2850b254ca..55263f49cef 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpHttpLogRecordExporterComponentProvider.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpHttpLogRecordExporterComponentProvider.java @@ -8,6 +8,7 @@ import static io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil.DATA_TYPE_LOGS; import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter; import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporterBuilder; import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; @@ -47,7 +48,9 @@ public LogRecordExporter create(DeclarativeConfigProperties config) { builder::setClientTls, builder::setRetryPolicy, builder::setMemoryMode, - /* isHttpProtobuf= */ true); + /* isHttpProtobuf= */ true, + builder::setInternalTelemetryVersion, + () -> builder.setMeterProvider(MeterProvider::noop)); return builder.build(); } diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpHttpMetricExporterComponentProvider.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpHttpMetricExporterComponentProvider.java index 0b59ee9380a..83c0126fdb8 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpHttpMetricExporterComponentProvider.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpHttpMetricExporterComponentProvider.java @@ -8,6 +8,7 @@ import static io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil.DATA_TYPE_METRICS; import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.exporter.internal.IncubatingExporterBuilderUtil; import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder; @@ -48,7 +49,9 @@ public MetricExporter create(DeclarativeConfigProperties config) { builder::setClientTls, builder::setRetryPolicy, builder::setMemoryMode, - /* isHttpProtobuf= */ true); + /* isHttpProtobuf= */ true, + builder::setInternalTelemetryVersion, + () -> builder.setMeterProvider(MeterProvider::noop)); IncubatingExporterBuilderUtil.configureOtlpAggregationTemporality( config, builder::setAggregationTemporalitySelector); IncubatingExporterBuilderUtil.configureOtlpHistogramDefaultAggregation( diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpHttpSpanExporterComponentProvider.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpHttpSpanExporterComponentProvider.java index d97b2e30628..f7815bd0ac0 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpHttpSpanExporterComponentProvider.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpHttpSpanExporterComponentProvider.java @@ -8,6 +8,7 @@ import static io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil.DATA_TYPE_TRACES; import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder; import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; @@ -47,7 +48,9 @@ public SpanExporter create(DeclarativeConfigProperties config) { builder::setClientTls, builder::setRetryPolicy, builder::setMemoryMode, - /* isHttpProtobuf= */ true); + /* isHttpProtobuf= */ true, + builder::setInternalTelemetryVersion, + () -> builder.setMeterProvider(MeterProvider::noop)); return builder.build(); } diff --git a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/ExtendedDeclarativeConfigProperties.java b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/ExtendedDeclarativeConfigProperties.java new file mode 100644 index 00000000000..c1c66f5103e --- /dev/null +++ b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/ExtendedDeclarativeConfigProperties.java @@ -0,0 +1,20 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.autoconfigure.spi.internal; + +import io.opentelemetry.api.incubator.config.ConfigProvider; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; + +/** + * Extended version of {@link DeclarativeConfigProperties} with access to {@link ConfigProvider}. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public interface ExtendedDeclarativeConfigProperties extends DeclarativeConfigProperties { + + ConfigProvider getConfigProvider(); +} diff --git a/sdk-extensions/autoconfigure/build.gradle.kts b/sdk-extensions/autoconfigure/build.gradle.kts index f78a0f12134..f0f89f58886 100644 --- a/sdk-extensions/autoconfigure/build.gradle.kts +++ b/sdk-extensions/autoconfigure/build.gradle.kts @@ -86,7 +86,12 @@ testing { dependencies { implementation(project(":sdk-extensions:incubator")) implementation(project(":exporters:logging")) + implementation(project(":exporters:otlp:all")) implementation(project(":sdk:testing")) + + implementation("io.opentelemetry.proto:opentelemetry-proto") + implementation("com.linecorp.armeria:armeria-junit5") + implementation("com.linecorp.armeria:armeria-grpc") } } diff --git a/sdk-extensions/autoconfigure/src/testIncubating/java/io/opentelemetry/sdk/autoconfigure/FullDeclarativeConfigTest.java b/sdk-extensions/autoconfigure/src/testIncubating/java/io/opentelemetry/sdk/autoconfigure/FullDeclarativeConfigTest.java new file mode 100644 index 00000000000..5257e5db457 --- /dev/null +++ b/sdk-extensions/autoconfigure/src/testIncubating/java/io/opentelemetry/sdk/autoconfigure/FullDeclarativeConfigTest.java @@ -0,0 +1,342 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.autoconfigure; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +import com.linecorp.armeria.server.ServerBuilder; +import com.linecorp.armeria.server.grpc.GrpcService; +import com.linecorp.armeria.server.logging.LoggingService; +import com.linecorp.armeria.testing.junit5.server.ServerExtension; +import io.grpc.stub.StreamObserver; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator; +import io.opentelemetry.api.logs.Logger; +import io.opentelemetry.api.logs.Severity; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest; +import io.opentelemetry.proto.collector.logs.v1.ExportLogsServiceResponse; +import io.opentelemetry.proto.collector.logs.v1.LogsServiceGrpc; +import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest; +import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse; +import io.opentelemetry.proto.collector.metrics.v1.MetricsServiceGrpc; +import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; +import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse; +import io.opentelemetry.proto.collector.trace.v1.TraceServiceGrpc; +import io.opentelemetry.proto.common.v1.AnyValue; +import io.opentelemetry.proto.common.v1.KeyValue; +import io.opentelemetry.proto.metrics.v1.Metric; +import io.opentelemetry.proto.metrics.v1.ScopeMetrics; +import io.opentelemetry.proto.trace.v1.Span; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.io.TempDir; + +/** Same as {@code FullConfigTest}, but using declarative configuration. */ +@SuppressWarnings("InterruptedExceptionSwallowed") +public class FullDeclarativeConfigTest { + + private static final BlockingQueue otlpTraceRequests = + new LinkedBlockingDeque<>(); + private static final BlockingQueue otlpMetricsRequests = + new LinkedBlockingDeque<>(); + private static final BlockingQueue otlpLogsRequests = + new LinkedBlockingDeque<>(); + + @RegisterExtension + public static final ServerExtension server = + new ServerExtension() { + @Override + protected void configure(ServerBuilder sb) { + sb.service( + GrpcService.builder() + // OTLP spans + .addService( + new TraceServiceGrpc.TraceServiceImplBase() { + @Override + public void export( + ExportTraceServiceRequest request, + StreamObserver responseObserver) { + otlpTraceRequests.add(request); + responseObserver.onNext(ExportTraceServiceResponse.getDefaultInstance()); + responseObserver.onCompleted(); + } + }) + // OTLP metrics + .addService( + new MetricsServiceGrpc.MetricsServiceImplBase() { + @Override + public void export( + ExportMetricsServiceRequest request, + StreamObserver responseObserver) { + if (request.getResourceMetricsCount() > 0) { + otlpMetricsRequests.add(request); + } + responseObserver.onNext( + ExportMetricsServiceResponse.getDefaultInstance()); + responseObserver.onCompleted(); + } + }) + // OTLP logs + .addService( + new LogsServiceGrpc.LogsServiceImplBase() { + @Override + public void export( + ExportLogsServiceRequest request, + StreamObserver responseObserver) { + if (request.getResourceLogsCount() > 0) { + otlpLogsRequests.add(request); + } + responseObserver.onNext(ExportLogsServiceResponse.getDefaultInstance()); + responseObserver.onCompleted(); + } + }) + .useBlockingTaskExecutor(true) + .build()); + sb.decorator(LoggingService.newDecorator()); + } + }; + + private OpenTelemetrySdk openTelemetrySdk; + + @BeforeEach + void setUp(@TempDir Path tempDir) throws IOException { + otlpTraceRequests.clear(); + otlpMetricsRequests.clear(); + otlpLogsRequests.clear(); + + String endpoint = "http://localhost:" + server.httpPort(); + String yaml = + "file_format: \"1.0-rc.1\"\n" + + "resource:\n" + + " attributes:\n" + + " - name: service.name\n" + + " value: test\n" + + "propagator:\n" + + " composite:\n" + + " - tracecontext:\n" + + " - baggage:\n" + + "tracer_provider:\n" + + " processors:\n" + + " - batch:\n" + + " schedule_delay: 60000\n" // High delay for reliable export batches after + // flushing + + " exporter:\n" + + " otlp_grpc:\n" + + " endpoint: " + + endpoint + + "\n" + + "meter_provider:\n" + + " readers:\n" + + " - periodic:\n" + + " exporter:\n" + + " otlp_grpc:\n" + + " endpoint: " + + endpoint + + "\n" + + "logger_provider:\n" + + " processors:\n" + + " - batch:\n" + + " schedule_delay: 60000\n" // High delay for reliable export batches after + // flushing + + " exporter:\n" + + " otlp_grpc:\n" + + " endpoint: " + + endpoint + + "\n" + + "instrumentation/development:\n" + + " java:\n" + + " otel_sdk:\n" + + " internal_telemetry_version: latest\n"; + + Path path = tempDir.resolve("otel-config.yaml"); + Files.write(path, yaml.getBytes(StandardCharsets.UTF_8)); + + // Initialize here so we can shutdown when done + GlobalOpenTelemetry.resetForTest(); + ConfigProperties config = + DefaultConfigProperties.createFromMap( + Collections.singletonMap("otel.experimental.config.file", path.toString())); + openTelemetrySdk = + AutoConfiguredOpenTelemetrySdk.builder() + .setConfig(config) + .setResultAsGlobal() + .build() + .getOpenTelemetrySdk(); + } + + @AfterEach + void afterEach() { + openTelemetrySdk.close(); + GlobalOpenTelemetry.resetForTest(); + } + + @Test + void configures() throws Exception { + Collection fields = + GlobalOpenTelemetry.get().getPropagators().getTextMapPropagator().fields(); + List keys = new ArrayList<>(); + keys.addAll(W3CTraceContextPropagator.getInstance().fields()); + keys.addAll(W3CBaggagePropagator.getInstance().fields()); + assertThat(fields).containsExactlyInAnyOrderElementsOf(keys); + + GlobalOpenTelemetry.get() + .getTracer("test") + .spanBuilder("test") + .startSpan() + .setAttribute("cat", "meow") + .end(); + + Meter meter = GlobalOpenTelemetry.get().getMeter("test"); + meter.counterBuilder("my-metric").build().add(1); + + Logger logger = GlobalOpenTelemetry.get().getLogsBridge().get("test"); + logger.logRecordBuilder().setBody("info log message").setSeverity(Severity.INFO).emit(); + + openTelemetrySdk.getSdkTracerProvider().forceFlush().join(10, TimeUnit.SECONDS); + openTelemetrySdk.getSdkLoggerProvider().forceFlush().join(10, TimeUnit.SECONDS); + openTelemetrySdk.getSdkMeterProvider().forceFlush().join(10, TimeUnit.SECONDS); + + await().untilAsserted(() -> assertThat(otlpTraceRequests).hasSize(1)); + + ExportTraceServiceRequest traceRequest = otlpTraceRequests.take(); + List spanResourceAttributes = + traceRequest.getResourceSpans(0).getResource().getAttributesList(); + assertHasKeyValue(spanResourceAttributes, "service.name", "test"); + Span span = traceRequest.getResourceSpans(0).getScopeSpans(0).getSpans(0); + assertHasKeyValue(span.getAttributesList(), "cat", "meow"); + + // Flush again to get metric exporter metrics. + openTelemetrySdk.getSdkMeterProvider().forceFlush().join(10, TimeUnit.SECONDS); + // await on assertions since metrics may come in different order for BatchSpanProcessor, + // exporter, or the ones we created in the test. + await() + .untilAsserted( + () -> { + ExportMetricsServiceRequest metricRequest = otlpMetricsRequests.take(); + + assertThat(metricRequest.getResourceMetricsList()) + .satisfiesExactly( + resourceMetrics -> { + List metricResourceAttributes = + resourceMetrics.getResource().getAttributesList(); + assertHasKeyValue(metricResourceAttributes, "service.name", "test"); + assertThat(resourceMetrics.getScopeMetricsList()) + .anySatisfy( + scopeMetrics -> { + assertThat(scopeMetrics.getScope().getName()).isEqualTo("test"); + assertMetricNames(scopeMetrics, "my-metric"); + }) + // This verifies that MeterProvider was injected into OTLP exporters and + // the internal telemetry version was set. + .anySatisfy( + scopeMetrics -> { + assertThat(scopeMetrics.getScope().getName()) + .isEqualTo( + "io.opentelemetry.exporters.otlp_grpc_metric_exporter"); + assertMetricNames( + scopeMetrics, + "otel.sdk.exporter.metric_data_point.inflight", + "otel.sdk.exporter.operation.duration", + "otel.sdk.exporter.metric_data_point.exported"); + }) + .anySatisfy( + scopeMetrics -> { + assertThat(scopeMetrics.getScope().getName()) + .isEqualTo( + "io.opentelemetry.exporters.otlp_grpc_log_exporter"); + assertMetricNames( + scopeMetrics, + "otel.sdk.exporter.log.inflight", + "otel.sdk.exporter.operation.duration", + "otel.sdk.exporter.log.exported"); + }) + .anySatisfy( + scopeMetrics -> { + assertThat(scopeMetrics.getScope().getName()) + .isEqualTo( + "io.opentelemetry.exporters.otlp_grpc_span_exporter"); + assertMetricNames( + scopeMetrics, + "otel.sdk.exporter.span.inflight", + "otel.sdk.exporter.operation.duration", + "otel.sdk.exporter.span.exported"); + }) + .anySatisfy( + scopeMetrics -> { + assertThat(scopeMetrics.getScope().getName()) + .isEqualTo("io.opentelemetry.sdk.logs"); + assertMetricNames( + scopeMetrics, + "otel.sdk.log.created", + "otel.sdk.processor.log.processed", + "otel.sdk.processor.log.queue.capacity", + "otel.sdk.processor.log.queue.size"); + }) + .anySatisfy( + scopeMetrics -> { + assertThat(scopeMetrics.getScope().getName()) + .isEqualTo("io.opentelemetry.sdk.trace"); + assertMetricNames( + scopeMetrics, + "otel.sdk.span.live", + "otel.sdk.span.started", + "otel.sdk.processor.span.processed", + "otel.sdk.processor.span.queue.capacity", + "otel.sdk.processor.span.queue.size"); + }); + }); + }); + + await().untilAsserted(() -> assertThat(otlpLogsRequests).hasSize(1)); + ExportLogsServiceRequest logRequest = otlpLogsRequests.take(); + List logResourceAttributes = + logRequest.getResourceLogs(0).getResource().getAttributesList(); + assertHasKeyValue(logResourceAttributes, "service.name", "test"); + + assertThat(logRequest.getResourceLogs(0).getScopeLogs(0).getLogRecordsList()) + .satisfiesExactlyInAnyOrder( + logRecord -> { + assertThat(logRecord.getBody().getStringValue()).isEqualTo("info log message"); + assertThat(logRecord.getSeverityNumberValue()) + .isEqualTo(Severity.INFO.getSeverityNumber()); + }); + } + + private static void assertHasKeyValue(List keyValues, String key, String value) { + assertThat(keyValues) + .contains( + KeyValue.newBuilder() + .setKey(key) + .setValue(AnyValue.newBuilder().setStringValue(value)) + .build()); + } + + private static void assertMetricNames(ScopeMetrics scopeMetrics, String... names) { + assertThat( + scopeMetrics.getMetricsList().stream().map(Metric::getName).collect(Collectors.toSet())) + .containsExactlyInAnyOrder(names); + } +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigContext.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigContext.java index 2bdafca7adc..ddecba22df1 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigContext.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigContext.java @@ -5,17 +5,25 @@ package io.opentelemetry.sdk.extension.incubator.fileconfig; +import io.opentelemetry.api.incubator.config.ConfigProvider; import io.opentelemetry.api.incubator.config.DeclarativeConfigException; import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.common.ComponentLoader; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ExtendedDeclarativeConfigProperties; +import io.opentelemetry.sdk.common.InternalTelemetryVersion; import io.opentelemetry.sdk.resources.Resource; import java.io.Closeable; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Supplier; import java.util.stream.Collectors; import javax.annotation.Nullable; @@ -26,6 +34,7 @@ class DeclarativeConfigContext { private final List closeables = new ArrayList<>(); @Nullable private volatile MeterProvider meterProvider; @Nullable private Resource resource = null; + @Nullable private ConfigProvider configProvider; @Nullable private List componentProviders = null; // Visible for testing @@ -50,15 +59,14 @@ List getCloseables() { return Collections.unmodifiableList(closeables); } - @Nullable - public MeterProvider getMeterProvider() { - return meterProvider; - } - public void setMeterProvider(MeterProvider meterProvider) { this.meterProvider = meterProvider; } + public void setConfigProvider(ConfigProvider configProvider) { + this.configProvider = configProvider; + } + Resource getResource() { // called via reflection from io.opentelemetry.sdk.autoconfigure.IncubatingUtil if (resource == null) { @@ -71,6 +79,54 @@ void setResource(Resource resource) { this.resource = resource; } + /** + * Overload of {@link #setInternalTelemetry(Consumer, Consumer)} for components which do not + * support setting {@link InternalTelemetryVersion} because they only support {@link + * InternalTelemetryVersion#LATEST}. + */ + public void setInternalTelemetry(Consumer> meterProviderSetter) { + setInternalTelemetry(meterProviderSetter, unused -> {}); + } + + /** + * Set internal telemetry on built-in components. + * + * @param meterProviderSetter the component meter provider setter + * @param internalTelemetrySetter the component internal telemetry setter + */ + public void setInternalTelemetry( + Consumer> meterProviderSetter, + Consumer internalTelemetrySetter) { + InternalTelemetryVersion telemetryVersion = getInternalTelemetryVersion(); + if (telemetryVersion != null) { + meterProviderSetter.accept(() -> Objects.requireNonNull(meterProvider)); + internalTelemetrySetter.accept(telemetryVersion); + } else { + meterProviderSetter.accept(MeterProvider::noop); + } + } + + @Nullable + private InternalTelemetryVersion getInternalTelemetryVersion() { + if (configProvider == null) { + return null; + } + String internalTelemetryVersion = + configProvider.getInstrumentationConfig("otel_sdk").getString("internal_telemetry_version"); + if (internalTelemetryVersion == null) { + return null; + } + switch (internalTelemetryVersion.toLowerCase(Locale.ROOT)) { + case "legacy": + return InternalTelemetryVersion.LEGACY; + case "latest": + return InternalTelemetryVersion.LATEST; + default: + throw new DeclarativeConfigException( + "Invalid sdk telemetry version: " + internalTelemetryVersion); + } + } + SpiHelper getSpiHelper() { return spiHelper; } @@ -86,7 +142,10 @@ SpiHelper getSpiHelper() { @SuppressWarnings({"unchecked"}) T loadComponent(Class type, ConfigKeyValue configKeyValue) { String name = configKeyValue.getKey(); - DeclarativeConfigProperties config = configKeyValue.getValue(); + ExtendedDeclarativeConfigProperties config = + new ExtendedDeclarativeConfigPropertiesImpl( + configKeyValue.getValue(), + configProvider == null ? ConfigProvider.noop() : configProvider); if (componentProviders == null) { componentProviders = spiHelper.load(ComponentProvider.class); @@ -137,4 +196,85 @@ T loadComponent(Class type, ConfigKeyValue configKeyValue) { "Error configuring " + type.getName() + " with name \"" + name + "\"", throwable); } } + + private static class ExtendedDeclarativeConfigPropertiesImpl + implements ExtendedDeclarativeConfigProperties { + + private final DeclarativeConfigProperties delegate; + private final ConfigProvider configProvider; + + ExtendedDeclarativeConfigPropertiesImpl( + DeclarativeConfigProperties delegate, ConfigProvider configProvider) { + this.delegate = delegate; + this.configProvider = configProvider; + } + + @Override + public ConfigProvider getConfigProvider() { + return configProvider; + } + + @Nullable + @Override + public String getString(String name) { + return delegate.getString(name); + } + + @Nullable + @Override + public Boolean getBoolean(String name) { + return delegate.getBoolean(name); + } + + @Nullable + @Override + public Integer getInt(String name) { + return delegate.getInt(name); + } + + @Nullable + @Override + public Long getLong(String name) { + return delegate.getLong(name); + } + + @Nullable + @Override + public Double getDouble(String name) { + return delegate.getDouble(name); + } + + @Nullable + @Override + public List getScalarList(String name, Class scalarType) { + return delegate.getScalarList(name, scalarType); + } + + @Nullable + @Override + public DeclarativeConfigProperties getStructured(String name) { + return delegate.getStructured(name); + } + + @Nullable + @Override + public List getStructuredList(String name) { + return delegate.getStructuredList(name); + } + + @Override + public Set getPropertyKeys() { + return delegate.getPropertyKeys(); + } + + @Override + public ComponentLoader getComponentLoader() { + return delegate.getComponentLoader(); + } + + @Override + public String toString() { + return "ExtendedDeclarativeConfigPropertiesImpl{" + delegate + '}'; + } + } } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordProcessorFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordProcessorFactory.java index 22d53510bca..402c058640c 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordProcessorFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordProcessorFactory.java @@ -5,7 +5,6 @@ package io.opentelemetry.sdk.extension.incubator.fileconfig; -import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchLogRecordProcessorModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordExporterModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordProcessorModel; @@ -15,6 +14,7 @@ import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessorBuilder; import io.opentelemetry.sdk.logs.export.LogRecordExporter; import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor; +import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessorBuilder; import java.time.Duration; final class LogRecordProcessorFactory @@ -67,10 +67,7 @@ private static LogRecordProcessor createBatchLogRecordProcessor( if (batchModel.getScheduleDelay() != null) { builder.setScheduleDelay(Duration.ofMillis(batchModel.getScheduleDelay())); } - MeterProvider meterProvider = context.getMeterProvider(); - if (meterProvider != null) { - builder.setMeterProvider(meterProvider); - } + context.setInternalTelemetry(builder::setMeterProvider, builder::setInternalTelemetryVersion); return context.addCloseable(builder.build()); } @@ -82,10 +79,8 @@ private static LogRecordProcessor createSimpleLogRecordProcessor( simpleModel.getExporter(), "simple log record processor exporter"); LogRecordExporter logRecordExporter = LogRecordExporterFactory.getInstance().create(exporterModel, context); - MeterProvider meterProvider = context.getMeterProvider(); - return context.addCloseable( - SimpleLogRecordProcessor.builder(logRecordExporter) - .setMeterProvider(() -> meterProvider) - .build()); + SimpleLogRecordProcessorBuilder builder = SimpleLogRecordProcessor.builder(logRecordExporter); + context.setInternalTelemetry(builder::setMeterProvider); + return context.addCloseable(builder.build()); } } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LoggerProviderFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LoggerProviderFactory.java index 892dc65c77a..b206b4937a6 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LoggerProviderFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LoggerProviderFactory.java @@ -45,6 +45,8 @@ public SdkLoggerProviderBuilder create( return builder; } + context.setInternalTelemetry(builder::setMeterProvider); + LogLimits logLimits = LogLimitsFactory.getInstance() .create( diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java index 7506018662c..6244f70fcb2 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java @@ -43,6 +43,7 @@ public ExtendedOpenTelemetrySdk create( DeclarativeConfiguration.toConfigProperties( model, context.getSpiHelper().getComponentLoader()); SdkConfigProvider sdkConfigProvider = SdkConfigProvider.create(modelProperties); + context.setConfigProvider(sdkConfigProvider); OpenTelemetrySdkBuilder builder = OpenTelemetrySdkBuilderUtil.setConfigProvider( OpenTelemetrySdk.builder(), sdkConfigProvider); diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanProcessorFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanProcessorFactory.java index 75b0a557cc3..8bdee9134e9 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanProcessorFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanProcessorFactory.java @@ -5,7 +5,6 @@ package io.opentelemetry.sdk.extension.incubator.fileconfig; -import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchSpanProcessorModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SimpleSpanProcessorModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SpanExporterModel; @@ -63,10 +62,7 @@ private static SpanProcessor createBatchLogRecordProcessor( if (batchModel.getScheduleDelay() != null) { builder.setScheduleDelay(Duration.ofMillis(batchModel.getScheduleDelay())); } - MeterProvider meterProvider = context.getMeterProvider(); - if (meterProvider != null) { - builder.setMeterProvider(() -> meterProvider); - } + context.setInternalTelemetry(builder::setMeterProvider, builder::setInternalTelemetryVersion); return context.addCloseable(builder.build()); } @@ -77,10 +73,7 @@ private static SpanProcessor createSimpleLogRecordProcessor( FileConfigUtil.requireNonNull(simpleModel.getExporter(), "simple span processor exporter"); SpanExporter spanExporter = SpanExporterFactory.getInstance().create(exporterModel, context); SimpleSpanProcessorBuilder builder = SimpleSpanProcessor.builder(spanExporter); - MeterProvider meterProvider = context.getMeterProvider(); - if (meterProvider != null) { - builder.setMeterProvider(() -> meterProvider); - } + context.setInternalTelemetry(builder::setMeterProvider); return context.addCloseable(builder.build()); } } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/TracerProviderFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/TracerProviderFactory.java index 4e28bb25c73..6fcf4034609 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/TracerProviderFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/TracerProviderFactory.java @@ -42,6 +42,8 @@ public SdkTracerProviderBuilder create( return builder; } + context.setInternalTelemetry(builder::setMeterProvider); + SpanLimits spanLimits = SpanLimitsFactory.getInstance() .create( diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigurationParseTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigurationParseTest.java index 3498f9ca9db..f256b9ca466 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigurationParseTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigurationParseTest.java @@ -1158,7 +1158,7 @@ void sysPropertySubstituteAndLoadYaml(String rawYaml, Object expectedYamlResult) @SuppressWarnings("unchecked") private static Stream sysPropertySubstitutionArgs() { - return java.util.stream.Stream.of( + return Stream.of( // Simple cases with sys: prefix Arguments.of("key1: ${sys:str.1}\n", mapOf(entry("key1", "value1"))), Arguments.of("key1: ${sys:bool.prop}\n", mapOf(entry("key1", true))),