From 9a13f6880d53a19344d0e49dbd6c7daadbbab68a Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Mon, 3 Jun 2024 16:03:04 -0500 Subject: [PATCH 1/2] Add file configuration ComponentProvider support for exporters --- .../internal/ExporterBuilderUtil.java | 17 +++ ...oleLogRecordExporterComponentProvider.java | 36 +++++ ...onsoleMetricExporterComponentProvider.java | 36 +++++ .../ConsoleSpanExporterComponentProvider.java | 35 +++++ ...toconfigure.spi.internal.ComponentProvider | 3 + .../otlp/internal/OtlpConfigUtil.java | 126 +++++++++++++++++ ...tlpLogRecordExporterComponentProvider.java | 90 +++++++++++++ .../OtlpMetricExporterComponentProvider.java | 97 +++++++++++++ .../OtlpSpanExporterComponentProvider.java | 89 ++++++++++++ ...toconfigure.spi.internal.ComponentProvider | 3 + .../ZipkinSpanExporterComponentProvider.java | 48 +++++++ ...toconfigure.spi.internal.ComponentProvider | 1 + .../spi/internal/ComponentProvider.java | 7 +- .../sdk/autoconfigure/internal/SpiHelper.java | 51 +++++++ .../autoconfigure/FileConfigurationTest.java | 3 +- .../incubator/fileconfig/FileConfigUtil.java | 40 +----- .../fileconfig/LogRecordExporterFactory.java | 88 ++++-------- .../fileconfig/LogRecordProcessorFactory.java | 22 ++- .../fileconfig/MetricExporterFactory.java | 102 +++----------- .../fileconfig/MetricReaderFactory.java | 5 +- .../fileconfig/SpanExporterFactory.java | 117 ++++------------ .../fileconfig/SpanProcessorFactory.java | 23 ++-- .../FileConfigurationCreateTest.java | 3 +- .../LogRecordExporterFactoryTest.java | 101 ++++++++------ .../LogRecordProcessorFactoryTest.java | 44 +++--- .../fileconfig/MetricExporterFactoryTest.java | 108 ++++++++------- .../fileconfig/SpanExporterFactoryTest.java | 127 ++++++++++-------- .../fileconfig/SpanProcessorFactoryTest.java | 44 +++--- .../LogRecordExporterComponentProvider.java | 54 ++++++++ .../MetricExporterComponentProvider.java | 63 +++++++++ .../SpanExporterComponentProvider.java | 54 ++++++++ ...toconfigure.spi.internal.ComponentProvider | 3 + 32 files changed, 1131 insertions(+), 509 deletions(-) create mode 100644 exporters/logging/src/main/java/io/opentelemetry/exporter/logging/internal/ConsoleLogRecordExporterComponentProvider.java create mode 100644 exporters/logging/src/main/java/io/opentelemetry/exporter/logging/internal/ConsoleMetricExporterComponentProvider.java create mode 100644 exporters/logging/src/main/java/io/opentelemetry/exporter/logging/internal/ConsoleSpanExporterComponentProvider.java create mode 100644 exporters/logging/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider create mode 100644 exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpLogRecordExporterComponentProvider.java create mode 100644 exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterComponentProvider.java create mode 100644 exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpSpanExporterComponentProvider.java create mode 100644 exporters/otlp/all/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider create mode 100644 exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ZipkinSpanExporterComponentProvider.java create mode 100644 exporters/zipkin/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider create mode 100644 sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/component/LogRecordExporterComponentProvider.java create mode 100644 sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/component/MetricExporterComponentProvider.java create mode 100644 sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/component/SpanExporterComponentProvider.java create mode 100644 sdk-extensions/incubator/src/test/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/ExporterBuilderUtil.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/ExporterBuilderUtil.java index b19aad8e131..79256cfd788 100644 --- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/ExporterBuilderUtil.java +++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/ExporterBuilderUtil.java @@ -7,6 +7,7 @@ import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; import io.opentelemetry.sdk.common.export.MemoryMode; import java.net.URI; import java.net.URISyntaxException; @@ -54,5 +55,21 @@ public static void configureExporterMemoryMode( memoryModeConsumer.accept(memoryMode); } + /** Invoke the {@code memoryModeConsumer} with the configured {@link MemoryMode}. */ + public static void configureExporterMemoryMode( + StructuredConfigProperties config, Consumer memoryModeConsumer) { + String memoryModeStr = config.getString("memory_mode"); + if (memoryModeStr == null) { + return; + } + MemoryMode memoryMode; + try { + memoryMode = MemoryMode.valueOf(memoryModeStr.toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException e) { + throw new ConfigurationException("Unrecognized memory_mode: " + memoryModeStr, e); + } + memoryModeConsumer.accept(memoryMode); + } + private ExporterBuilderUtil() {} } diff --git a/exporters/logging/src/main/java/io/opentelemetry/exporter/logging/internal/ConsoleLogRecordExporterComponentProvider.java b/exporters/logging/src/main/java/io/opentelemetry/exporter/logging/internal/ConsoleLogRecordExporterComponentProvider.java new file mode 100644 index 00000000000..4d03480b335 --- /dev/null +++ b/exporters/logging/src/main/java/io/opentelemetry/exporter/logging/internal/ConsoleLogRecordExporterComponentProvider.java @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.logging.internal; + +import io.opentelemetry.exporter.logging.SystemOutLogRecordExporter; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; +import io.opentelemetry.sdk.logs.export.LogRecordExporter; + +/** + * File configuration SPI implementation for {@link SystemOutLogRecordExporter}. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public final class ConsoleLogRecordExporterComponentProvider + implements ComponentProvider { + + @Override + public Class getType() { + return LogRecordExporter.class; + } + + @Override + public String getName() { + return "console"; + } + + @Override + public LogRecordExporter create(StructuredConfigProperties config) { + return SystemOutLogRecordExporter.create(); + } +} diff --git a/exporters/logging/src/main/java/io/opentelemetry/exporter/logging/internal/ConsoleMetricExporterComponentProvider.java b/exporters/logging/src/main/java/io/opentelemetry/exporter/logging/internal/ConsoleMetricExporterComponentProvider.java new file mode 100644 index 00000000000..48a449ff0f0 --- /dev/null +++ b/exporters/logging/src/main/java/io/opentelemetry/exporter/logging/internal/ConsoleMetricExporterComponentProvider.java @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.logging.internal; + +import io.opentelemetry.exporter.logging.LoggingMetricExporter; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; +import io.opentelemetry.sdk.metrics.export.MetricExporter; + +/** + * File configuration SPI implementation for {@link LoggingMetricExporter}. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public final class ConsoleMetricExporterComponentProvider + implements ComponentProvider { + + @Override + public Class getType() { + return MetricExporter.class; + } + + @Override + public String getName() { + return "console"; + } + + @Override + public MetricExporter create(StructuredConfigProperties config) { + return LoggingMetricExporter.create(); + } +} diff --git a/exporters/logging/src/main/java/io/opentelemetry/exporter/logging/internal/ConsoleSpanExporterComponentProvider.java b/exporters/logging/src/main/java/io/opentelemetry/exporter/logging/internal/ConsoleSpanExporterComponentProvider.java new file mode 100644 index 00000000000..c212a77d5d4 --- /dev/null +++ b/exporters/logging/src/main/java/io/opentelemetry/exporter/logging/internal/ConsoleSpanExporterComponentProvider.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.logging.internal; + +import io.opentelemetry.exporter.logging.LoggingSpanExporter; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; +import io.opentelemetry.sdk.trace.export.SpanExporter; + +/** + * File configuration SPI implementation for {@link LoggingSpanExporter}. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public final class ConsoleSpanExporterComponentProvider implements ComponentProvider { + + @Override + public Class getType() { + return SpanExporter.class; + } + + @Override + public String getName() { + return "console"; + } + + @Override + public SpanExporter create(StructuredConfigProperties config) { + return LoggingSpanExporter.create(); + } +} diff --git a/exporters/logging/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider b/exporters/logging/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider new file mode 100644 index 00000000000..ed0c3a77f5e --- /dev/null +++ b/exporters/logging/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider @@ -0,0 +1,3 @@ +io.opentelemetry.exporter.logging.internal.ConsoleMetricExporterComponentProvider +io.opentelemetry.exporter.logging.internal.ConsoleSpanExporterComponentProvider +io.opentelemetry.exporter.logging.internal.ConsoleLogRecordExporterComponentProvider diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpConfigUtil.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpConfigUtil.java index 2b387d06367..693caa69d29 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpConfigUtil.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpConfigUtil.java @@ -10,6 +10,7 @@ import io.opentelemetry.exporter.internal.ExporterBuilderUtil; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; import io.opentelemetry.sdk.common.export.MemoryMode; import io.opentelemetry.sdk.common.export.RetryPolicy; import io.opentelemetry.sdk.metrics.Aggregation; @@ -54,6 +55,11 @@ public static String getOtlpProtocol(String dataType, ConfigProperties config) { return config.getString("otel.exporter.otlp.protocol", PROTOCOL_GRPC); } + /** Determine the configured OTLP protocol for the {@code dataType}. */ + public static String getStructuredConfigOtlpProtocol(StructuredConfigProperties config) { + return config.getString("protocol", PROTOCOL_GRPC); + } + /** Invoke the setters with the OTLP configuration for the {@code dataType}. */ @SuppressWarnings("TooManyParameters") public static void configureOtlpExporterBuilder( @@ -160,6 +166,79 @@ public static void configureOtlpExporterBuilder( ExporterBuilderUtil.configureExporterMemoryMode(config, setMemoryMode); } + /** Invoke the setters with the OTLP configuration for the {@code dataType}. */ + @SuppressWarnings("TooManyParameters") + public static void configureOtlpExporterBuilder( + String dataType, + StructuredConfigProperties config, + Consumer setEndpoint, + BiConsumer addHeader, + Consumer setCompression, + Consumer setTimeout, + Consumer setTrustedCertificates, + BiConsumer setClientTls, + Consumer setRetryPolicy, + Consumer setMemoryMode) { + String protocol = getStructuredConfigOtlpProtocol(config); + boolean isHttpProtobuf = protocol.equals(PROTOCOL_HTTP_PROTOBUF); + URL endpoint = validateEndpoint(config.getString("endpoint"), isHttpProtobuf); + if (endpoint != null && isHttpProtobuf) { + String path = endpoint.getPath(); + if (!path.endsWith("/")) { + path += "/"; + } + path += signalPath(dataType); + endpoint = createUrl(endpoint, path); + } + if (endpoint != null) { + setEndpoint.accept(endpoint.toString()); + } + + StructuredConfigProperties headers = config.getStructured("headers"); + if (headers != null) { + headers + .getPropertyKeys() + .forEach( + header -> { + String value = headers.getString(header); + if (value != null) { + addHeader.accept(header, value); + } + }); + } + + String compression = config.getString("compression"); + if (compression != null) { + setCompression.accept(compression); + } + + Integer timeoutMs = config.getInt("timeout"); + if (timeoutMs != null) { + setTimeout.accept(Duration.ofMillis(timeoutMs)); + } + + String certificatePath = config.getString("certificate"); + String clientKeyPath = config.getString("client_key"); + String clientKeyChainPath = config.getString("client_certificate"); + + if (clientKeyPath != null && clientKeyChainPath == null) { + throw new ConfigurationException("client_key provided but client_certificate"); + } else if (clientKeyPath == null && clientKeyChainPath != null) { + throw new ConfigurationException("client_certificate provided but client_key"); + } + byte[] certificateBytes = readFileBytes(certificatePath); + if (certificateBytes != null) { + setTrustedCertificates.accept(certificateBytes); + } + byte[] clientKeyBytes = readFileBytes(clientKeyPath); + byte[] clientKeyChainBytes = readFileBytes(clientKeyChainPath); + if (clientKeyBytes != null && clientKeyChainBytes != null) { + setClientTls.accept(clientKeyBytes, clientKeyChainBytes); + } + + ExporterBuilderUtil.configureExporterMemoryMode(config, setMemoryMode); + } + /** * Invoke the {@code aggregationTemporalitySelectorConsumer} with the configured {@link * AggregationTemporality}. @@ -188,6 +267,30 @@ public static void configureOtlpAggregationTemporality( aggregationTemporalitySelectorConsumer.accept(temporalitySelector); } + public static void configureOtlpAggregationTemporality( + StructuredConfigProperties config, + Consumer aggregationTemporalitySelectorConsumer) { + String temporalityStr = config.getString("temporality_preference"); + if (temporalityStr == null) { + return; + } + AggregationTemporalitySelector temporalitySelector; + switch (temporalityStr.toLowerCase(Locale.ROOT)) { + case "cumulative": + temporalitySelector = AggregationTemporalitySelector.alwaysCumulative(); + break; + case "delta": + temporalitySelector = AggregationTemporalitySelector.deltaPreferred(); + break; + case "lowmemory": + temporalitySelector = AggregationTemporalitySelector.lowMemory(); + break; + default: + throw new ConfigurationException("Unrecognized temporality_preference: " + temporalityStr); + } + aggregationTemporalitySelectorConsumer.accept(temporalitySelector); + } + /** * Invoke the {@code defaultAggregationSelectorConsumer} with the configured {@link * DefaultAggregationSelector}. @@ -212,6 +315,29 @@ public static void configureOtlpHistogramDefaultAggregation( } } + /** + * Invoke the {@code defaultAggregationSelectorConsumer} with the configured {@link + * DefaultAggregationSelector}. + */ + public static void configureOtlpHistogramDefaultAggregation( + StructuredConfigProperties config, + Consumer defaultAggregationSelectorConsumer) { + String defaultHistogramAggregation = config.getString("default_histogram_aggregation"); + if (defaultHistogramAggregation == null) { + return; + } + if (AggregationUtil.aggregationName(Aggregation.base2ExponentialBucketHistogram()) + .equalsIgnoreCase(defaultHistogramAggregation)) { + defaultAggregationSelectorConsumer.accept( + DefaultAggregationSelector.getDefault() + .with(InstrumentType.HISTOGRAM, Aggregation.base2ExponentialBucketHistogram())); + } else if (!AggregationUtil.aggregationName(explicitBucketHistogram()) + .equalsIgnoreCase(defaultHistogramAggregation)) { + throw new ConfigurationException( + "Unrecognized default_histogram_aggregation: " + defaultHistogramAggregation); + } + } + private static URL createUrl(URL context, String spec) { try { return new URL(context, spec); diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpLogRecordExporterComponentProvider.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpLogRecordExporterComponentProvider.java new file mode 100644 index 00000000000..cd7d97a1d46 --- /dev/null +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpLogRecordExporterComponentProvider.java @@ -0,0 +1,90 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.otlp.internal; + +import static io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil.DATA_TYPE_LOGS; +import static io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil.PROTOCOL_GRPC; +import static io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil.PROTOCOL_HTTP_PROTOBUF; + +import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter; +import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporterBuilder; +import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter; +import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporterBuilder; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; +import io.opentelemetry.sdk.logs.export.LogRecordExporter; + +/** + * File configuration SPI implementation for {@link OtlpHttpLogRecordExporter} and {@link + * OtlpGrpcLogRecordExporter}. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public class OtlpLogRecordExporterComponentProvider + implements ComponentProvider { + + @Override + public Class getType() { + return LogRecordExporter.class; + } + + @Override + public String getName() { + return "otlp"; + } + + @Override + public LogRecordExporter create(StructuredConfigProperties config) { + String protocol = OtlpConfigUtil.getStructuredConfigOtlpProtocol(config); + + if (protocol.equals(PROTOCOL_HTTP_PROTOBUF)) { + OtlpHttpLogRecordExporterBuilder builder = httpBuilder(); + + OtlpConfigUtil.configureOtlpExporterBuilder( + DATA_TYPE_LOGS, + config, + builder::setEndpoint, + builder::addHeader, + builder::setCompression, + builder::setTimeout, + builder::setTrustedCertificates, + builder::setClientTls, + builder::setRetryPolicy, + builder::setMemoryMode); + + return builder.build(); + } else if (protocol.equals(PROTOCOL_GRPC)) { + OtlpGrpcLogRecordExporterBuilder builder = grpcBuilder(); + + OtlpConfigUtil.configureOtlpExporterBuilder( + DATA_TYPE_LOGS, + config, + builder::setEndpoint, + builder::addHeader, + builder::setCompression, + builder::setTimeout, + builder::setTrustedCertificates, + builder::setClientTls, + builder::setRetryPolicy, + builder::setMemoryMode); + + return builder.build(); + } + throw new ConfigurationException("Unsupported OTLP metrics protocol: " + protocol); + } + + // Visible for testing + OtlpHttpLogRecordExporterBuilder httpBuilder() { + return OtlpHttpLogRecordExporter.builder(); + } + + // Visible for testing + OtlpGrpcLogRecordExporterBuilder grpcBuilder() { + return OtlpGrpcLogRecordExporter.builder(); + } +} diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterComponentProvider.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterComponentProvider.java new file mode 100644 index 00000000000..bac3b205835 --- /dev/null +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterComponentProvider.java @@ -0,0 +1,97 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.otlp.internal; + +import static io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil.DATA_TYPE_METRICS; +import static io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil.PROTOCOL_GRPC; +import static io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil.PROTOCOL_HTTP_PROTOBUF; + +import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; +import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder; +import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; +import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; +import io.opentelemetry.sdk.metrics.export.MetricExporter; + +/** + * File configuration SPI implementation for {@link OtlpHttpMetricExporter} and {@link + * OtlpGrpcMetricExporter}. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public class OtlpMetricExporterComponentProvider implements ComponentProvider { + + @Override + public Class getType() { + return MetricExporter.class; + } + + @Override + public String getName() { + return "otlp"; + } + + @Override + public MetricExporter create(StructuredConfigProperties config) { + String protocol = OtlpConfigUtil.getStructuredConfigOtlpProtocol(config); + + if (protocol.equals(PROTOCOL_HTTP_PROTOBUF)) { + OtlpHttpMetricExporterBuilder builder = httpBuilder(); + + OtlpConfigUtil.configureOtlpExporterBuilder( + DATA_TYPE_METRICS, + config, + builder::setEndpoint, + builder::addHeader, + builder::setCompression, + builder::setTimeout, + builder::setTrustedCertificates, + builder::setClientTls, + builder::setRetryPolicy, + builder::setMemoryMode); + OtlpConfigUtil.configureOtlpAggregationTemporality( + config, builder::setAggregationTemporalitySelector); + OtlpConfigUtil.configureOtlpHistogramDefaultAggregation( + config, builder::setDefaultAggregationSelector); + + return builder.build(); + } else if (protocol.equals(PROTOCOL_GRPC)) { + OtlpGrpcMetricExporterBuilder builder = grpcBuilder(); + + OtlpConfigUtil.configureOtlpExporterBuilder( + DATA_TYPE_METRICS, + config, + builder::setEndpoint, + builder::addHeader, + builder::setCompression, + builder::setTimeout, + builder::setTrustedCertificates, + builder::setClientTls, + builder::setRetryPolicy, + builder::setMemoryMode); + OtlpConfigUtil.configureOtlpAggregationTemporality( + config, builder::setAggregationTemporalitySelector); + OtlpConfigUtil.configureOtlpHistogramDefaultAggregation( + config, builder::setDefaultAggregationSelector); + + return builder.build(); + } + throw new ConfigurationException("Unsupported OTLP metrics protocol: " + protocol); + } + + // Visible for testing + OtlpHttpMetricExporterBuilder httpBuilder() { + return OtlpHttpMetricExporter.builder(); + } + + // Visible for testing + OtlpGrpcMetricExporterBuilder grpcBuilder() { + return OtlpGrpcMetricExporter.builder(); + } +} diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpSpanExporterComponentProvider.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpSpanExporterComponentProvider.java new file mode 100644 index 00000000000..707ff0c4383 --- /dev/null +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpSpanExporterComponentProvider.java @@ -0,0 +1,89 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.otlp.internal; + +import static io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil.DATA_TYPE_TRACES; +import static io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil.PROTOCOL_GRPC; +import static io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil.PROTOCOL_HTTP_PROTOBUF; + +import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; +import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder; +import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; +import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; +import io.opentelemetry.sdk.trace.export.SpanExporter; + +/** + * File configuration SPI implementation for {@link OtlpHttpSpanExporter} and {@link + * OtlpGrpcSpanExporter}. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public class OtlpSpanExporterComponentProvider implements ComponentProvider { + + @Override + public Class getType() { + return SpanExporter.class; + } + + @Override + public String getName() { + return "otlp"; + } + + @Override + public SpanExporter create(StructuredConfigProperties config) { + String protocol = OtlpConfigUtil.getStructuredConfigOtlpProtocol(config); + + if (protocol.equals(PROTOCOL_HTTP_PROTOBUF)) { + OtlpHttpSpanExporterBuilder builder = httpBuilder(); + + OtlpConfigUtil.configureOtlpExporterBuilder( + DATA_TYPE_TRACES, + config, + builder::setEndpoint, + builder::addHeader, + builder::setCompression, + builder::setTimeout, + builder::setTrustedCertificates, + builder::setClientTls, + builder::setRetryPolicy, + builder::setMemoryMode); + + return builder.build(); + } else if (protocol.equals(PROTOCOL_GRPC)) { + OtlpGrpcSpanExporterBuilder builder = grpcBuilder(); + + OtlpConfigUtil.configureOtlpExporterBuilder( + DATA_TYPE_TRACES, + config, + builder::setEndpoint, + builder::addHeader, + builder::setCompression, + builder::setTimeout, + builder::setTrustedCertificates, + builder::setClientTls, + builder::setRetryPolicy, + builder::setMemoryMode); + + return builder.build(); + } + throw new ConfigurationException("Unsupported OTLP metrics protocol: " + protocol); + } + + // Visible for testing + OtlpHttpSpanExporterBuilder httpBuilder() { + return OtlpHttpSpanExporter.builder(); + } + + // Visible for testing + OtlpGrpcSpanExporterBuilder grpcBuilder() { + return OtlpGrpcSpanExporter.builder(); + } +} diff --git a/exporters/otlp/all/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider b/exporters/otlp/all/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider new file mode 100644 index 00000000000..239060d1f1e --- /dev/null +++ b/exporters/otlp/all/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider @@ -0,0 +1,3 @@ +io.opentelemetry.exporter.otlp.internal.OtlpMetricExporterComponentProvider +io.opentelemetry.exporter.otlp.internal.OtlpSpanExporterComponentProvider +io.opentelemetry.exporter.otlp.internal.OtlpLogRecordExporterComponentProvider diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ZipkinSpanExporterComponentProvider.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ZipkinSpanExporterComponentProvider.java new file mode 100644 index 00000000000..eafd62479f0 --- /dev/null +++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ZipkinSpanExporterComponentProvider.java @@ -0,0 +1,48 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.zipkin.internal; + +import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter; +import io.opentelemetry.exporter.zipkin.ZipkinSpanExporterBuilder; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import java.time.Duration; + +/** + * File configuration SPI implementation for {@link ZipkinSpanExporter}. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public class ZipkinSpanExporterComponentProvider implements ComponentProvider { + @Override + public Class getType() { + return SpanExporter.class; + } + + @Override + public String getName() { + return "zipkin"; + } + + @Override + public SpanExporter create(StructuredConfigProperties config) { + ZipkinSpanExporterBuilder builder = ZipkinSpanExporter.builder(); + + String endpoint = config.getString("endpoint"); + if (endpoint != null) { + builder.setEndpoint(endpoint); + } + + Long timeoutMs = config.getLong("timeout"); + if (timeoutMs != null) { + builder.setReadTimeout(Duration.ofMillis(timeoutMs)); + } + + return builder.build(); + } +} diff --git a/exporters/zipkin/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider b/exporters/zipkin/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider new file mode 100644 index 00000000000..08330b52dad --- /dev/null +++ b/exporters/zipkin/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider @@ -0,0 +1 @@ +io.opentelemetry.exporter.zipkin.internal.ZipkinSpanExporterComponentProvider diff --git a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/ComponentProvider.java b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/ComponentProvider.java index 344dc18267c..fe1e310abc9 100644 --- a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/ComponentProvider.java +++ b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/ComponentProvider.java @@ -5,6 +5,8 @@ package io.opentelemetry.sdk.autoconfigure.spi.internal; +import io.opentelemetry.sdk.logs.export.LogRecordExporter; +import io.opentelemetry.sdk.metrics.export.MetricExporter; import io.opentelemetry.sdk.trace.export.SpanExporter; /** @@ -12,9 +14,10 @@ * extension components which are not part of the core SDK to be referenced in file based * configuration. * - * @param the type of the SDK extension component. See {@link #getType()}. + * @param the type of the SDK extension component. See {@link #getType()}. Supported values + * include: {@link SpanExporter}, {@link MetricExporter}, {@link LogRecordExporter}. */ -// TODO (jack-berg): list the specific types which are supported in file configuration +// TODO: add support for Sampler, LogRecordProcessor, SpanProcessor, MetricReader public interface ComponentProvider { /** diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/internal/SpiHelper.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/internal/SpiHelper.java index d54e526d44a..bd3bfc13f04 100644 --- a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/internal/SpiHelper.java +++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/internal/SpiHelper.java @@ -6,8 +6,11 @@ package io.opentelemetry.sdk.autoconfigure.internal; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; import io.opentelemetry.sdk.autoconfigure.spi.Ordered; import io.opentelemetry.sdk.autoconfigure.spi.internal.AutoConfigureListener; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -20,6 +23,7 @@ import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Collectors; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at @@ -76,6 +80,53 @@ public NamedSpiManager loadConfigurable( return NamedSpiManager.create(nameToProvider); } + /** + * Find a registered {@link ComponentProvider} which {@link ComponentProvider#getType()} matching + * {@code type}, {@link ComponentProvider#getName()} matching {@code name}, and call {@link + * ComponentProvider#create(StructuredConfigProperties)} with the given {@code model}. + * + * @throws ConfigurationException if no matching providers are found, or if multiple are found + * (i.e. conflict), or if {@link ComponentProvider#create(StructuredConfigProperties)} throws + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public T loadComponent(Class type, String name, StructuredConfigProperties config) { + // TODO(jack-berg): cache loaded component providers + List componentProviders = load(ComponentProvider.class); + List> matchedProviders = + componentProviders.stream() + .map( + (Function>) + componentProvider -> componentProvider) + .filter( + componentProvider -> + componentProvider.getType() == type && name.equals(componentProvider.getName())) + .collect(Collectors.toList()); + if (matchedProviders.isEmpty()) { + throw new ConfigurationException( + "No component provider detected for " + type.getName() + " with name \"" + name + "\"."); + } + if (matchedProviders.size() > 1) { + throw new ConfigurationException( + "Component provider conflict. Multiple providers detected for " + + type.getName() + + " with name \"" + + name + + "\": " + + componentProviders.stream() + .map(provider -> provider.getClass().getName()) + .collect(Collectors.joining(",", "[", "]"))); + } + // Exactly one matching component provider + ComponentProvider provider = (ComponentProvider) matchedProviders.get(0); + + try { + return provider.create(config); + } catch (Throwable throwable) { + throw new ConfigurationException( + "Error configuring " + type.getName() + " with name \"" + name + "\"", throwable); + } + } + /** * Load implementations of an ordered SPI (i.e. implements {@link Ordered}). * diff --git a/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/FileConfigurationTest.java b/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/FileConfigurationTest.java index 639f2955da3..f9234de4c65 100644 --- a/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/FileConfigurationTest.java +++ b/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/FileConfigurationTest.java @@ -185,7 +185,8 @@ void configFile_Error(@TempDir Path tempDir) throws IOException { assertThatThrownBy(() -> AutoConfiguredOpenTelemetrySdk.builder().setConfig(config).build()) .isInstanceOf(ConfigurationException.class) - .hasMessage("Unrecognized span exporter(s): [foo]"); + .hasMessage( + "No component provider detected for io.opentelemetry.sdk.trace.export.SpanExporter with name \"foo\"."); } @Test diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigUtil.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigUtil.java index 7b5bcd9ca2d..4dfdcbd5565 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigUtil.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigUtil.java @@ -11,8 +11,6 @@ import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; import java.io.Closeable; import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; import javax.annotation.Nullable; final class FileConfigUtil { @@ -42,45 +40,9 @@ static T assertNotNull(@Nullable T object, String description) { * @throws ConfigurationException if no matching providers are found, or if multiple are found * (i.e. conflict), or if {@link ComponentProvider#create(StructuredConfigProperties)} throws */ - @SuppressWarnings({"unchecked", "rawtypes"}) static T loadComponent(SpiHelper spiHelper, Class type, String name, Object model) { - // TODO(jack-berg): cache loaded component providers - List componentProviders = spiHelper.load(ComponentProvider.class); - List> matchedProviders = - componentProviders.stream() - .map( - (Function>) - componentProvider -> componentProvider) - .filter( - componentProvider -> - componentProvider.getType() == type && name.equals(componentProvider.getName())) - .collect(Collectors.toList()); - if (matchedProviders.isEmpty()) { - throw new ConfigurationException( - "No component provider detected for " + type.getName() + " with name \"" + name + "\"."); - } - if (matchedProviders.size() > 1) { - throw new ConfigurationException( - "Component provider conflict. Multiple providers detected for " - + type.getName() - + " with name \"" - + name - + "\": " - + componentProviders.stream() - .map(provider -> provider.getClass().getName()) - .collect(Collectors.joining(",", "[", "]"))); - } - // Exactly one matching component provider - ComponentProvider provider = (ComponentProvider) matchedProviders.get(0); - // Map model to generic structured config properties StructuredConfigProperties config = FileConfiguration.toConfigProperties(model); - - try { - return provider.create(config); - } catch (Throwable throwable) { - throw new ConfigurationException( - "Error configuring " + type.getName() + " with name \"" + name + "\"", throwable); - } + return spiHelper.loadComponent(type, name, config); } } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordExporterFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordExporterFactory.java index 3469ec61e36..f930c2846ef 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordExporterFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordExporterFactory.java @@ -7,16 +7,11 @@ import static java.util.stream.Collectors.joining; -import io.opentelemetry.sdk.autoconfigure.internal.NamedSpiManager; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; -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.logs.ConfigurableLogRecordExporterProvider; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Otlp; import io.opentelemetry.sdk.logs.export.LogRecordExporter; import java.io.Closeable; -import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.Nullable; @@ -34,7 +29,9 @@ static LogRecordExporterFactory getInstance() { return INSTANCE; } + @SuppressWarnings("NullAway") // Override superclass non-null response @Override + @Nullable public LogRecordExporter create( @Nullable io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordExporter @@ -42,73 +39,36 @@ public LogRecordExporter create( SpiHelper spiHelper, List closeables) { if (model == null) { - return LogRecordExporter.composite(); + return null; } Otlp otlpModel = model.getOtlp(); if (otlpModel != null) { - return FileConfigUtil.addAndReturn(closeables, createOtlpExporter(otlpModel, spiHelper)); + model.getAdditionalProperties().put("otlp", otlpModel); } - // TODO(jack-berg): add support for generic SPI exporters if (!model.getAdditionalProperties().isEmpty()) { - throw new ConfigurationException( - "Unrecognized log record exporter(s): " - + model.getAdditionalProperties().keySet().stream().collect(joining(",", "[", "]"))); + Map additionalProperties = model.getAdditionalProperties(); + if (additionalProperties.size() > 1) { + throw new ConfigurationException( + "Invalid configuration - multiple log record exporters set: " + + additionalProperties.keySet().stream().collect(joining(",", "[", "]"))); + } + Map.Entry exporterKeyValue = + additionalProperties.entrySet().stream() + .findFirst() + .orElseThrow( + () -> + new IllegalStateException("Missing exporter. This is a programming error.")); + LogRecordExporter logRecordExporter = + FileConfigUtil.loadComponent( + spiHelper, + LogRecordExporter.class, + exporterKeyValue.getKey(), + exporterKeyValue.getValue()); + return FileConfigUtil.addAndReturn(closeables, logRecordExporter); } - return LogRecordExporter.composite(); - } - - private static LogRecordExporter createOtlpExporter(Otlp otlp, SpiHelper spiHelper) { - // Translate from file configuration scheme to environment variable scheme. This is ultimately - // interpreted by Otlp*ExporterProviders, but we want to avoid the dependency on - // opentelemetry-exporter-otlp - Map properties = new HashMap<>(); - if (otlp.getProtocol() != null) { - properties.put("otel.exporter.otlp.logs.protocol", otlp.getProtocol()); - } - if (otlp.getEndpoint() != null) { - // NOTE: Set general otel.exporter.otlp.endpoint instead of signal specific - // otel.exporter.otlp.logs.endpoint to allow signal path (i.e. /v1/logs) to be added if not - // present - properties.put("otel.exporter.otlp.endpoint", otlp.getEndpoint()); - } - if (otlp.getHeaders() != null) { - properties.put( - "otel.exporter.otlp.logs.headers", - otlp.getHeaders().getAdditionalProperties().entrySet().stream() - .map(entry -> entry.getKey() + "=" + entry.getValue()) - .collect(joining(","))); - } - if (otlp.getCompression() != null) { - properties.put("otel.exporter.otlp.logs.compression", otlp.getCompression()); - } - if (otlp.getTimeout() != null) { - properties.put("otel.exporter.otlp.logs.timeout", Integer.toString(otlp.getTimeout())); - } - if (otlp.getCertificate() != null) { - properties.put("otel.exporter.otlp.logs.certificate", otlp.getCertificate()); - } - if (otlp.getClientKey() != null) { - properties.put("otel.exporter.otlp.logs.client.key", otlp.getClientKey()); - } - if (otlp.getClientCertificate() != null) { - properties.put("otel.exporter.otlp.logs.client.certificate", otlp.getClientCertificate()); - } - - ConfigProperties configProperties = DefaultConfigProperties.createFromMap(properties); - return FileConfigUtil.assertNotNull( - logRecordExporterSpiManager(configProperties, spiHelper).getByName("otlp"), - "otlp exporter"); - } - - private static NamedSpiManager logRecordExporterSpiManager( - ConfigProperties config, SpiHelper spiHelper) { - return spiHelper.loadConfigurable( - ConfigurableLogRecordExporterProvider.class, - ConfigurableLogRecordExporterProvider::getName, - ConfigurableLogRecordExporterProvider::createExporter, - config); + return null; } } 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 5971d8537a9..aaf472beec7 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 @@ -47,13 +47,12 @@ public LogRecordProcessor create( batchModel = model.getBatch(); if (batchModel != null) { LogRecordExporter exporterModel = batchModel.getExporter(); - if (exporterModel == null) { - return LogRecordProcessor.composite(); + io.opentelemetry.sdk.logs.export.LogRecordExporter logRecordExporter = + LogRecordExporterFactory.getInstance().create(exporterModel, spiHelper, closeables); + if (logRecordExporter == null) { + throw new ConfigurationException("exporter required for batch log record processor"); } - - BatchLogRecordProcessorBuilder builder = - BatchLogRecordProcessor.builder( - LogRecordExporterFactory.getInstance().create(exporterModel, spiHelper, closeables)); + BatchLogRecordProcessorBuilder builder = BatchLogRecordProcessor.builder(logRecordExporter); if (batchModel.getExportTimeout() != null) { builder.setExporterTimeout(Duration.ofMillis(batchModel.getExportTimeout())); } @@ -73,14 +72,13 @@ public LogRecordProcessor create( simpleModel = model.getSimple(); if (simpleModel != null) { LogRecordExporter exporterModel = simpleModel.getExporter(); - if (exporterModel == null) { - return LogRecordProcessor.composite(); + io.opentelemetry.sdk.logs.export.LogRecordExporter logRecordExporter = + LogRecordExporterFactory.getInstance().create(exporterModel, spiHelper, closeables); + if (logRecordExporter == null) { + throw new ConfigurationException("exporter required for simple log record processor"); } - return FileConfigUtil.addAndReturn( - closeables, - SimpleLogRecordProcessor.create( - LogRecordExporterFactory.getInstance().create(exporterModel, spiHelper, closeables))); + closeables, SimpleLogRecordProcessor.create(logRecordExporter)); } // TODO: add support for generic log record processors diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricExporterFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricExporterFactory.java index 7d187f8a057..7bba4b843f0 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricExporterFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricExporterFactory.java @@ -7,17 +7,11 @@ import static java.util.stream.Collectors.joining; -import io.opentelemetry.sdk.autoconfigure.internal.NamedSpiManager; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; -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.metrics.ConfigurableMetricExporterProvider; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OtlpMetric; import io.opentelemetry.sdk.metrics.export.MetricExporter; import java.io.Closeable; -import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.Nullable; @@ -49,93 +43,39 @@ public MetricExporter create( OtlpMetric otlpModel = model.getOtlp(); if (otlpModel != null) { - return FileConfigUtil.addAndReturn(closeables, createOtlpExporter(otlpModel, spiHelper)); + model.getAdditionalProperties().put("otlp", otlpModel); } if (model.getConsole() != null) { - return FileConfigUtil.addAndReturn(closeables, createConsoleExporter(spiHelper)); + model.getAdditionalProperties().put("console", model.getConsole()); } if (model.getPrometheus() != null) { throw new ConfigurationException("prometheus exporter not supported in this context"); } - // TODO(jack-berg): add support for generic SPI exporters if (!model.getAdditionalProperties().isEmpty()) { - throw new ConfigurationException( - "Unrecognized metric exporter(s): " - + model.getAdditionalProperties().keySet().stream().collect(joining(",", "[", "]"))); + Map additionalProperties = model.getAdditionalProperties(); + if (additionalProperties.size() > 1) { + throw new ConfigurationException( + "Invalid configuration - multiple metric exporters set: " + + additionalProperties.keySet().stream().collect(joining(",", "[", "]"))); + } + Map.Entry exporterKeyValue = + additionalProperties.entrySet().stream() + .findFirst() + .orElseThrow( + () -> + new IllegalStateException("Missing exporter. This is a programming error.")); + MetricExporter metricExporter = + FileConfigUtil.loadComponent( + spiHelper, + MetricExporter.class, + exporterKeyValue.getKey(), + exporterKeyValue.getValue()); + return FileConfigUtil.addAndReturn(closeables, metricExporter); } return null; } - - private static MetricExporter createOtlpExporter(OtlpMetric model, SpiHelper spiHelper) { - // Translate from file configuration scheme to environment variable scheme. This is ultimately - // interpreted by Otlp*ExporterProviders, but we want to avoid the dependency on - // opentelemetry-exporter-otlp - Map properties = new HashMap<>(); - if (model.getProtocol() != null) { - properties.put("otel.exporter.otlp.metrics.protocol", model.getProtocol()); - } - if (model.getEndpoint() != null) { - // NOTE: Set general otel.exporter.otlp.endpoint instead of signal specific - // otel.exporter.otlp.metrics.endpoint to allow signal path (i.e. /v1/metrics) to be added - // if not - // present - properties.put("otel.exporter.otlp.endpoint", model.getEndpoint()); - } - if (model.getHeaders() != null) { - properties.put( - "otel.exporter.otlp.metrics.headers", - model.getHeaders().getAdditionalProperties().entrySet().stream() - .map(entry -> entry.getKey() + "=" + entry.getValue()) - .collect(joining(","))); - } - if (model.getCompression() != null) { - properties.put("otel.exporter.otlp.metrics.compression", model.getCompression()); - } - if (model.getTimeout() != null) { - properties.put("otel.exporter.otlp.metrics.timeout", Integer.toString(model.getTimeout())); - } - if (model.getCertificate() != null) { - properties.put("otel.exporter.otlp.metrics.certificate", model.getCertificate()); - } - if (model.getClientKey() != null) { - properties.put("otel.exporter.otlp.metrics.client.key", model.getClientKey()); - } - if (model.getClientCertificate() != null) { - properties.put("otel.exporter.otlp.metrics.client.certificate", model.getClientCertificate()); - } - if (model.getDefaultHistogramAggregation() != null) { - properties.put( - "otel.exporter.otlp.metrics.default.histogram.aggregation", - model.getDefaultHistogramAggregation().value()); - } - if (model.getTemporalityPreference() != null) { - properties.put( - "otel.exporter.otlp.metrics.temporality.preference", model.getTemporalityPreference()); - } - - ConfigProperties configProperties = DefaultConfigProperties.createFromMap(properties); - return FileConfigUtil.assertNotNull( - metricExporterSpiManager(configProperties, spiHelper).getByName("otlp"), "otlp exporter"); - } - - private static MetricExporter createConsoleExporter(SpiHelper spiHelper) { - return FileConfigUtil.assertNotNull( - metricExporterSpiManager( - DefaultConfigProperties.createFromMap(Collections.emptyMap()), spiHelper) - .getByName("logging"), - "logging exporter"); - } - - private static NamedSpiManager metricExporterSpiManager( - ConfigProperties config, SpiHelper spiHelper) { - return spiHelper.loadConfigurable( - ConfigurableMetricExporterProvider.class, - ConfigurableMetricExporterProvider::getName, - ConfigurableMetricExporterProvider::createExporter, - config); - } } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactory.java index 77750920cf2..3a049ab2689 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactory.java @@ -46,13 +46,10 @@ public MetricReader create( PeriodicMetricReader periodicModel = model.getPeriodic(); if (periodicModel != null) { MetricExporter exporterModel = periodicModel.getExporter(); - if (exporterModel == null) { - throw new ConfigurationException("exporter required for periodic reader"); - } io.opentelemetry.sdk.metrics.export.MetricExporter metricExporter = MetricExporterFactory.getInstance().create(exporterModel, spiHelper, closeables); if (metricExporter == null) { - return null; + throw new ConfigurationException("exporter required for periodic reader"); } PeriodicMetricReaderBuilder builder = io.opentelemetry.sdk.metrics.export.PeriodicMetricReader.builder( diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanExporterFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanExporterFactory.java index 8a3b8cc6dce..cf2c866bc3c 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanExporterFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanExporterFactory.java @@ -7,18 +7,12 @@ import static java.util.stream.Collectors.joining; -import io.opentelemetry.sdk.autoconfigure.internal.NamedSpiManager; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; -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.traces.ConfigurableSpanExporterProvider; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Otlp; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Zipkin; import io.opentelemetry.sdk.trace.export.SpanExporter; import java.io.Closeable; -import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.Nullable; @@ -36,113 +30,54 @@ static SpanExporterFactory getInstance() { return INSTANCE; } + @SuppressWarnings("NullAway") // Override superclass non-null response @Override + @Nullable public SpanExporter create( @Nullable io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SpanExporter model, SpiHelper spiHelper, List closeables) { if (model == null) { - return SpanExporter.composite(); + return null; } Otlp otlpModel = model.getOtlp(); if (otlpModel != null) { - return FileConfigUtil.addAndReturn(closeables, createOtlpExporter(otlpModel, spiHelper)); + model.getAdditionalProperties().put("otlp", otlpModel); } if (model.getConsole() != null) { - return FileConfigUtil.addAndReturn(closeables, createConsoleExporter(spiHelper)); + model.getAdditionalProperties().put("console", model.getConsole()); } Zipkin zipkinModel = model.getZipkin(); if (zipkinModel != null) { - return FileConfigUtil.addAndReturn(closeables, createZipkinExporter(zipkinModel, spiHelper)); + model.getAdditionalProperties().put("zipkin", model.getZipkin()); } - // TODO(jack-berg): add support for generic SPI exporters if (!model.getAdditionalProperties().isEmpty()) { - throw new ConfigurationException( - "Unrecognized span exporter(s): " - + model.getAdditionalProperties().keySet().stream().collect(joining(",", "[", "]"))); + Map additionalProperties = model.getAdditionalProperties(); + if (additionalProperties.size() > 1) { + throw new ConfigurationException( + "Invalid configuration - multiple span exporters set: " + + additionalProperties.keySet().stream().collect(joining(",", "[", "]"))); + } + Map.Entry exporterKeyValue = + additionalProperties.entrySet().stream() + .findFirst() + .orElseThrow( + () -> + new IllegalStateException("Missing exporter. This is a programming error.")); + SpanExporter spanExporter = + FileConfigUtil.loadComponent( + spiHelper, + SpanExporter.class, + exporterKeyValue.getKey(), + exporterKeyValue.getValue()); + return FileConfigUtil.addAndReturn(closeables, spanExporter); } - return SpanExporter.composite(); - } - - private static SpanExporter createOtlpExporter(Otlp model, SpiHelper spiHelper) { - // Translate from file configuration scheme to environment variable scheme. This is ultimately - // interpreted by Otlp*ExporterProviders, but we want to avoid the dependency on - // opentelemetry-exporter-otlp - Map properties = new HashMap<>(); - if (model.getProtocol() != null) { - properties.put("otel.exporter.otlp.traces.protocol", model.getProtocol()); - } - if (model.getEndpoint() != null) { - // NOTE: Set general otel.exporter.otlp.endpoint instead of signal specific - // otel.exporter.otlp.traces.endpoint to allow signal path (i.e. /v1/traces) to be added if - // not present - properties.put("otel.exporter.otlp.endpoint", model.getEndpoint()); - } - if (model.getHeaders() != null) { - properties.put( - "otel.exporter.otlp.traces.headers", - model.getHeaders().getAdditionalProperties().entrySet().stream() - .map(entry -> entry.getKey() + "=" + entry.getValue()) - .collect(joining(","))); - } - if (model.getCompression() != null) { - properties.put("otel.exporter.otlp.traces.compression", model.getCompression()); - } - if (model.getTimeout() != null) { - properties.put("otel.exporter.otlp.traces.timeout", Integer.toString(model.getTimeout())); - } - if (model.getCertificate() != null) { - properties.put("otel.exporter.otlp.traces.certificate", model.getCertificate()); - } - if (model.getClientKey() != null) { - properties.put("otel.exporter.otlp.traces.client.key", model.getClientKey()); - } - if (model.getClientCertificate() != null) { - properties.put("otel.exporter.otlp.traces.client.certificate", model.getClientCertificate()); - } - - ConfigProperties configProperties = DefaultConfigProperties.createFromMap(properties); - return FileConfigUtil.assertNotNull( - spanExporterSpiManager(configProperties, spiHelper).getByName("otlp"), "otlp exporter"); - } - - private static SpanExporter createConsoleExporter(SpiHelper spiHelper) { - return FileConfigUtil.assertNotNull( - spanExporterSpiManager( - DefaultConfigProperties.createFromMap(Collections.emptyMap()), spiHelper) - .getByName("logging"), - "logging exporter"); - } - - private static SpanExporter createZipkinExporter(Zipkin model, SpiHelper spiHelper) { - // Translate from file configuration scheme to environment variable scheme. This is ultimately - // interpreted by ZipkinSpanExporterProvider, but we want to avoid the dependency on - // opentelemetry-exporter-zipkin - Map properties = new HashMap<>(); - if (model.getEndpoint() != null) { - properties.put("otel.exporter.zipkin.endpoint", model.getEndpoint()); - } - if (model.getTimeout() != null) { - properties.put("otel.exporter.zipkin.timeout", Integer.toString(model.getTimeout())); - } - - ConfigProperties configProperties = DefaultConfigProperties.createFromMap(properties); - return FileConfigUtil.assertNotNull( - spanExporterSpiManager(configProperties, spiHelper).getByName("zipkin"), "zipkin exporter"); - } - - private static NamedSpiManager spanExporterSpiManager( - ConfigProperties config, SpiHelper spiHelper) { - return spiHelper.loadConfigurable( - ConfigurableSpanExporterProvider.class, - ConfigurableSpanExporterProvider::getName, - ConfigurableSpanExporterProvider::createExporter, - config); + return null; } } 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 59b78370ae6..6536f0bf3ca 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 @@ -46,13 +46,12 @@ public SpanProcessor create( batchModel = model.getBatch(); if (batchModel != null) { SpanExporter exporterModel = batchModel.getExporter(); - if (exporterModel == null) { - return SpanProcessor.composite(); + io.opentelemetry.sdk.trace.export.SpanExporter spanExporter = + SpanExporterFactory.getInstance().create(exporterModel, spiHelper, closeables); + if (spanExporter == null) { + throw new ConfigurationException("exporter required for batch span processor"); } - - BatchSpanProcessorBuilder builder = - BatchSpanProcessor.builder( - SpanExporterFactory.getInstance().create(exporterModel, spiHelper, closeables)); + BatchSpanProcessorBuilder builder = BatchSpanProcessor.builder(spanExporter); if (batchModel.getExportTimeout() != null) { builder.setExporterTimeout(Duration.ofMillis(batchModel.getExportTimeout())); } @@ -72,14 +71,12 @@ public SpanProcessor create( simpleModel = model.getSimple(); if (simpleModel != null) { SpanExporter exporterModel = simpleModel.getExporter(); - if (exporterModel == null) { - return SpanProcessor.composite(); + io.opentelemetry.sdk.trace.export.SpanExporter spanExporter = + SpanExporterFactory.getInstance().create(exporterModel, spiHelper, closeables); + if (spanExporter == null) { + throw new ConfigurationException("exporter required for simple span processor"); } - - return FileConfigUtil.addAndReturn( - closeables, - SimpleSpanProcessor.create( - SpanExporterFactory.getInstance().create(exporterModel, spiHelper, closeables))); + return FileConfigUtil.addAndReturn(closeables, SimpleSpanProcessor.create(spanExporter)); } // TODO: add support for generic span processors diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigurationCreateTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigurationCreateTest.java index 7699752b109..dcd90cd60c4 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigurationCreateTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigurationCreateTest.java @@ -118,7 +118,8 @@ void parseAndCreate_Exception_CleansUpPartials() { FileConfiguration.parseAndCreate( new ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8)))) .isInstanceOf(ConfigurationException.class) - .hasMessage("Unrecognized log record exporter(s): [foo]"); + .hasMessage( + "No component provider detected for io.opentelemetry.sdk.logs.export.LogRecordExporter with name \"foo\"."); logCapturer.assertContains( "Error encountered interpreting configuration model. Closing partially configured components."); logCapturer.assertContains( diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordExporterFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordExporterFactoryTest.java index 721421c8552..7a8815d57ce 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordExporterFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordExporterFactoryTest.java @@ -8,20 +8,20 @@ import static io.opentelemetry.sdk.extension.incubator.fileconfig.FileConfigTestUtil.createTempFileWithContent; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.linecorp.armeria.testing.junit5.server.SelfSignedCertificateExtension; import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter; import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter; import io.opentelemetry.internal.testing.CleanupExtension; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; -import io.opentelemetry.sdk.autoconfigure.spi.logs.ConfigurableLogRecordExporterProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; +import io.opentelemetry.sdk.extension.incubator.fileconfig.component.LogRecordExporterComponentProvider; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Headers; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Otlp; import io.opentelemetry.sdk.logs.export.LogRecordExporter; @@ -58,12 +58,8 @@ class LogRecordExporterFactoryTest { @Test void create_Null() { - LogRecordExporter expectedExporter = LogRecordExporter.composite(); - - LogRecordExporter exporter = - LogRecordExporterFactory.getInstance().create(null, spiHelper, new ArrayList<>()); - - assertThat(exporter.toString()).isEqualTo(expectedExporter.toString()); + assertThat(LogRecordExporterFactory.getInstance().create(null, spiHelper, new ArrayList<>())) + .isNull(); } @Test @@ -86,19 +82,21 @@ void create_OtlpDefaults() { assertThat(exporter.toString()).isEqualTo(expectedExporter.toString()); - ArgumentCaptor configCaptor = ArgumentCaptor.forClass(ConfigProperties.class); + assertThat(exporter.toString()).isEqualTo(expectedExporter.toString()); + + ArgumentCaptor configCaptor = + ArgumentCaptor.forClass(StructuredConfigProperties.class); verify(spiHelper) - .loadConfigurable( - eq(ConfigurableLogRecordExporterProvider.class), any(), any(), configCaptor.capture()); - ConfigProperties configProperties = configCaptor.getValue(); - assertThat(configProperties.getString("otel.exporter.otlp.logs.protocol")).isNull(); - assertThat(configProperties.getString("otel.exporter.otlp.endpoint")).isNull(); - assertThat(configProperties.getMap("otel.exporter.otlp.logs.headers")).isEmpty(); - assertThat(configProperties.getString("otel.exporter.otlp.logs.compression")).isNull(); - assertThat(configProperties.getDuration("otel.exporter.otlp.logs.timeout")).isNull(); - assertThat(configProperties.getString("otel.exporter.otlp.logs.certificate")).isNull(); - assertThat(configProperties.getString("otel.exporter.otlp.logs.client.key")).isNull(); - assertThat(configProperties.getString("otel.exporter.otlp.logs.client.certificate")).isNull(); + .loadComponent(eq(LogRecordExporter.class), eq("otlp"), configCaptor.capture()); + StructuredConfigProperties configProperties = configCaptor.getValue(); + assertThat(configProperties.getString("protocol")).isNull(); + assertThat(configProperties.getString("endpoint")).isNull(); + assertThat(configProperties.getStructured("headers")).isNull(); + assertThat(configProperties.getString("compression")).isNull(); + assertThat(configProperties.getInt("timeout")).isNull(); + assertThat(configProperties.getString("certificate")).isNull(); + assertThat(configProperties.getString("client_key")).isNull(); + assertThat(configProperties.getString("client_certificate")).isNull(); } @Test @@ -151,30 +149,27 @@ void create_OtlpConfigured(@TempDir Path tempDir) assertThat(exporter.toString()).isEqualTo(expectedExporter.toString()); - ArgumentCaptor configCaptor = ArgumentCaptor.forClass(ConfigProperties.class); + ArgumentCaptor configCaptor = + ArgumentCaptor.forClass(StructuredConfigProperties.class); verify(spiHelper) - .loadConfigurable( - eq(ConfigurableLogRecordExporterProvider.class), any(), any(), configCaptor.capture()); - ConfigProperties configProperties = configCaptor.getValue(); - assertThat(configProperties.getString("otel.exporter.otlp.logs.protocol")) - .isEqualTo("http/protobuf"); - assertThat(configProperties.getString("otel.exporter.otlp.endpoint")) - .isEqualTo("http://example:4318"); - assertThat(configProperties.getMap("otel.exporter.otlp.logs.headers")) - .isEqualTo(ImmutableMap.of("key1", "value1", "key2", "value2")); - assertThat(configProperties.getString("otel.exporter.otlp.logs.compression")).isEqualTo("gzip"); - assertThat(configProperties.getDuration("otel.exporter.otlp.logs.timeout")) - .isEqualTo(Duration.ofSeconds(15)); - assertThat(configProperties.getString("otel.exporter.otlp.logs.certificate")) - .isEqualTo(certificatePath); - assertThat(configProperties.getString("otel.exporter.otlp.logs.client.key")) - .isEqualTo(clientKeyPath); - assertThat(configProperties.getString("otel.exporter.otlp.logs.client.certificate")) - .isEqualTo(clientCertificatePath); + .loadComponent(eq(LogRecordExporter.class), eq("otlp"), configCaptor.capture()); + StructuredConfigProperties configProperties = configCaptor.getValue(); + assertThat(configProperties.getString("protocol")).isEqualTo("http/protobuf"); + assertThat(configProperties.getString("endpoint")).isEqualTo("http://example:4318"); + StructuredConfigProperties headers = configProperties.getStructured("headers"); + assertThat(headers).isNotNull(); + assertThat(headers.getPropertyKeys()).isEqualTo(ImmutableSet.of("key1", "key2")); + assertThat(headers.getString("key1")).isEqualTo("value1"); + assertThat(headers.getString("key2")).isEqualTo("value2"); + assertThat(configProperties.getString("compression")).isEqualTo("gzip"); + assertThat(configProperties.getInt("timeout")).isEqualTo(Duration.ofSeconds(15).toMillis()); + assertThat(configProperties.getString("certificate")).isEqualTo(certificatePath); + assertThat(configProperties.getString("client_key")).isEqualTo(clientKeyPath); + assertThat(configProperties.getString("client_certificate")).isEqualTo(clientCertificatePath); } @Test - void create_SpiExporter() { + void create_SpiExporter_Unknown() { List closeables = new ArrayList<>(); assertThatThrownBy( @@ -183,11 +178,31 @@ void create_SpiExporter() { .create( new io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model .LogRecordExporter() - .withAdditionalProperty("test", ImmutableMap.of("key1", "value1")), + .withAdditionalProperty( + "unknown_key", ImmutableMap.of("key1", "value1")), spiHelper, new ArrayList<>())) .isInstanceOf(ConfigurationException.class) - .hasMessage("Unrecognized log record exporter(s): [test]"); + .hasMessage( + "No component provider detected for io.opentelemetry.sdk.logs.export.LogRecordExporter with name \"unknown_key\"."); cleanup.addCloseables(closeables); } + + @Test + void create_SpiExporter_Valid() { + LogRecordExporter logRecordExporter = + LogRecordExporterFactory.getInstance() + .create( + new io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model + .LogRecordExporter() + .withAdditionalProperty("test", ImmutableMap.of("key1", "value1")), + spiHelper, + new ArrayList<>()); + assertThat(logRecordExporter) + .isInstanceOf(LogRecordExporterComponentProvider.TestLogRecordExporter.class); + assertThat( + ((LogRecordExporterComponentProvider.TestLogRecordExporter) logRecordExporter) + .config.getString("key1")) + .isEqualTo("value1"); + } } diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordProcessorFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordProcessorFactoryTest.java index 043533117b2..7a9e632d2b3 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordProcessorFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordProcessorFactoryTest.java @@ -48,19 +48,15 @@ void create_Null() { @Test void create_BatchNullExporter() { - List closeables = new ArrayList<>(); - - io.opentelemetry.sdk.logs.LogRecordProcessor processor = - LogRecordProcessorFactory.getInstance() - .create( - new LogRecordProcessor().withBatch(new BatchLogRecordProcessor()), - spiHelper, - Collections.emptyList()); - cleanup.addCloseable(processor); - cleanup.addCloseables(closeables); - - assertThat(processor.toString()) - .isEqualTo(io.opentelemetry.sdk.logs.LogRecordProcessor.composite().toString()); + assertThatThrownBy( + () -> + LogRecordProcessorFactory.getInstance() + .create( + new LogRecordProcessor().withBatch(new BatchLogRecordProcessor()), + spiHelper, + Collections.emptyList())) + .isInstanceOf(ConfigurationException.class) + .hasMessage("exporter required for batch log record processor"); } @Test @@ -119,19 +115,15 @@ void create_BatchConfigured() { @Test void create_SimpleNullExporter() { - List closeables = new ArrayList<>(); - - io.opentelemetry.sdk.logs.LogRecordProcessor processor = - LogRecordProcessorFactory.getInstance() - .create( - new LogRecordProcessor().withSimple(new SimpleLogRecordProcessor()), - spiHelper, - Collections.emptyList()); - cleanup.addCloseable(processor); - cleanup.addCloseables(closeables); - - assertThat(processor.toString()) - .isEqualTo(io.opentelemetry.sdk.logs.LogRecordProcessor.composite().toString()); + assertThatThrownBy( + () -> + LogRecordProcessorFactory.getInstance() + .create( + new LogRecordProcessor().withSimple(new SimpleLogRecordProcessor()), + spiHelper, + Collections.emptyList())) + .isInstanceOf(ConfigurationException.class) + .hasMessage("exporter required for simple log record processor"); } @Test diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricExporterFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricExporterFactoryTest.java index 6470239bfdf..dd8cca69107 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricExporterFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricExporterFactoryTest.java @@ -8,21 +8,21 @@ import static io.opentelemetry.sdk.extension.incubator.fileconfig.FileConfigTestUtil.createTempFileWithContent; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.linecorp.armeria.testing.junit5.server.SelfSignedCertificateExtension; import io.opentelemetry.exporter.logging.LoggingMetricExporter; import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; import io.opentelemetry.internal.testing.CleanupExtension; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; -import io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; +import io.opentelemetry.sdk.extension.incubator.fileconfig.component.MetricExporterComponentProvider; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Console; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Headers; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OtlpMetric; @@ -89,25 +89,20 @@ void create_OtlpDefaults() { assertThat(exporter.toString()).isEqualTo(expectedExporter.toString()); - ArgumentCaptor configCaptor = ArgumentCaptor.forClass(ConfigProperties.class); - verify(spiHelper) - .loadConfigurable( - eq(ConfigurableMetricExporterProvider.class), any(), any(), configCaptor.capture()); - ConfigProperties configProperties = configCaptor.getValue(); - assertThat(configProperties.getString("otel.exporter.otlp.metrics.protocol")).isNull(); - assertThat(configProperties.getString("otel.exporter.otlp.endpoint")).isNull(); - assertThat(configProperties.getMap("otel.exporter.otlp.metrics.headers")).isEmpty(); - assertThat(configProperties.getString("otel.exporter.otlp.metrics.compression")).isNull(); - assertThat(configProperties.getDuration("otel.exporter.otlp.metrics.timeout")).isNull(); - assertThat(configProperties.getString("otel.exporter.otlp.metrics.certificate")).isNull(); - assertThat(configProperties.getString("otel.exporter.otlp.metrics.client.key")).isNull(); - assertThat(configProperties.getString("otel.exporter.otlp.metrics.client.certificate")) - .isNull(); - assertThat( - configProperties.getString("otel.exporter.otlp.metrics.default.histogram.aggregation")) - .isNull(); - assertThat(configProperties.getString("otel.exporter.otlp.metrics.temporality.preference")) - .isNull(); + ArgumentCaptor configCaptor = + ArgumentCaptor.forClass(StructuredConfigProperties.class); + verify(spiHelper).loadComponent(eq(MetricExporter.class), eq("otlp"), configCaptor.capture()); + StructuredConfigProperties configProperties = configCaptor.getValue(); + assertThat(configProperties.getString("protocol")).isNull(); + assertThat(configProperties.getString("endpoint")).isNull(); + assertThat(configProperties.getStructured("headers")).isNull(); + assertThat(configProperties.getString("compression")).isNull(); + assertThat(configProperties.getInt("timeout")).isNull(); + assertThat(configProperties.getString("certificate")).isNull(); + assertThat(configProperties.getString("client_key")).isNull(); + assertThat(configProperties.getString("client_certificate")).isNull(); + assertThat(configProperties.getString("temporality_preference")).isNull(); + assertThat(configProperties.getString("default_histogram_aggregation")).isNull(); } @Test @@ -167,31 +162,24 @@ void create_OtlpConfigured(@TempDir Path tempDir) assertThat(exporter.toString()).isEqualTo(expectedExporter.toString()); - ArgumentCaptor configCaptor = ArgumentCaptor.forClass(ConfigProperties.class); - verify(spiHelper) - .loadConfigurable( - eq(ConfigurableMetricExporterProvider.class), any(), any(), configCaptor.capture()); - ConfigProperties configProperties = configCaptor.getValue(); - assertThat(configProperties.getString("otel.exporter.otlp.metrics.protocol")) - .isEqualTo("http/protobuf"); - assertThat(configProperties.getString("otel.exporter.otlp.endpoint")) - .isEqualTo("http://example:4318"); - assertThat(configProperties.getMap("otel.exporter.otlp.metrics.headers")) - .isEqualTo(ImmutableMap.of("key1", "value1", "key2", "value2")); - assertThat(configProperties.getString("otel.exporter.otlp.metrics.compression")) - .isEqualTo("gzip"); - assertThat(configProperties.getDuration("otel.exporter.otlp.metrics.timeout")) - .isEqualTo(Duration.ofSeconds(15)); - assertThat(configProperties.getString("otel.exporter.otlp.metrics.certificate")) - .isEqualTo(certificatePath); - assertThat(configProperties.getString("otel.exporter.otlp.metrics.client.key")) - .isEqualTo(clientKeyPath); - assertThat(configProperties.getString("otel.exporter.otlp.metrics.client.certificate")) - .isEqualTo(clientCertificatePath); - assertThat(configProperties.getString("otel.exporter.otlp.metrics.temporality.preference")) - .isEqualTo("delta"); - assertThat( - configProperties.getString("otel.exporter.otlp.metrics.default.histogram.aggregation")) + ArgumentCaptor configCaptor = + ArgumentCaptor.forClass(StructuredConfigProperties.class); + verify(spiHelper).loadComponent(eq(MetricExporter.class), eq("otlp"), configCaptor.capture()); + StructuredConfigProperties configProperties = configCaptor.getValue(); + assertThat(configProperties.getString("protocol")).isEqualTo("http/protobuf"); + assertThat(configProperties.getString("endpoint")).isEqualTo("http://example:4318"); + StructuredConfigProperties headers = configProperties.getStructured("headers"); + assertThat(headers).isNotNull(); + assertThat(headers.getPropertyKeys()).isEqualTo(ImmutableSet.of("key1", "key2")); + assertThat(headers.getString("key1")).isEqualTo("value1"); + assertThat(headers.getString("key2")).isEqualTo("value2"); + assertThat(configProperties.getString("compression")).isEqualTo("gzip"); + assertThat(configProperties.getInt("timeout")).isEqualTo(Duration.ofSeconds(15).toMillis()); + assertThat(configProperties.getString("certificate")).isEqualTo(certificatePath); + assertThat(configProperties.getString("client_key")).isEqualTo(clientKeyPath); + assertThat(configProperties.getString("client_certificate")).isEqualTo(clientCertificatePath); + assertThat(configProperties.getString("temporality_preference")).isEqualTo("delta"); + assertThat(configProperties.getString("default_histogram_aggregation")) .isEqualTo("base2_exponential_bucket_histogram"); } @@ -235,7 +223,7 @@ void create_PrometheusExporter() { } @Test - void create_SpiExporter() { + void create_SpiExporter_Unknown() { List closeables = new ArrayList<>(); assertThatThrownBy( @@ -244,11 +232,31 @@ void create_SpiExporter() { .create( new io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model .MetricExporter() - .withAdditionalProperty("test", ImmutableMap.of("key1", "value1")), + .withAdditionalProperty( + "unknown_key", ImmutableMap.of("key1", "value1")), spiHelper, new ArrayList<>())) .isInstanceOf(ConfigurationException.class) - .hasMessage("Unrecognized metric exporter(s): [test]"); + .hasMessage( + "No component provider detected for io.opentelemetry.sdk.metrics.export.MetricExporter with name \"unknown_key\"."); cleanup.addCloseables(closeables); } + + @Test + void create_SpiExporter_Valid() { + MetricExporter metricExporter = + MetricExporterFactory.getInstance() + .create( + new io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model + .MetricExporter() + .withAdditionalProperty("test", ImmutableMap.of("key1", "value1")), + spiHelper, + new ArrayList<>()); + assertThat(metricExporter) + .isInstanceOf(MetricExporterComponentProvider.TestMetricExporter.class); + assertThat( + ((MetricExporterComponentProvider.TestMetricExporter) metricExporter) + .config.getString("key1")) + .isEqualTo("value1"); + } } diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanExporterFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanExporterFactoryTest.java index 09c47433003..0712e745d91 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanExporterFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanExporterFactoryTest.java @@ -8,12 +8,12 @@ import static io.opentelemetry.sdk.extension.incubator.fileconfig.FileConfigTestUtil.createTempFileWithContent; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.linecorp.armeria.testing.junit5.server.SelfSignedCertificateExtension; import io.opentelemetry.exporter.logging.LoggingSpanExporter; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; @@ -21,9 +21,9 @@ import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter; import io.opentelemetry.internal.testing.CleanupExtension; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; -import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; +import io.opentelemetry.sdk.extension.incubator.fileconfig.component.SpanExporterComponentProvider; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Console; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Headers; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Otlp; @@ -59,6 +59,12 @@ class SpanExporterFactoryTest { private SpiHelper spiHelper = SpiHelper.create(SpanExporterFactoryTest.class.getClassLoader()); + @Test + void create_Null() { + assertThat(SpanExporterFactory.getInstance().create(null, spiHelper, new ArrayList<>())) + .isNull(); + } + @Test void create_OtlpDefaults() { spiHelper = spy(spiHelper); @@ -79,19 +85,18 @@ void create_OtlpDefaults() { assertThat(exporter.toString()).isEqualTo(expectedExporter.toString()); - ArgumentCaptor configCaptor = ArgumentCaptor.forClass(ConfigProperties.class); - verify(spiHelper) - .loadConfigurable( - eq(ConfigurableSpanExporterProvider.class), any(), any(), configCaptor.capture()); - ConfigProperties configProperties = configCaptor.getValue(); - assertThat(configProperties.getString("otel.exporter.otlp.traces.protocol")).isNull(); - assertThat(configProperties.getString("otel.exporter.otlp.endpoint")).isNull(); - assertThat(configProperties.getMap("otel.exporter.otlp.traces.headers")).isEmpty(); - assertThat(configProperties.getString("otel.exporter.otlp.traces.compression")).isNull(); - assertThat(configProperties.getDuration("otel.exporter.otlp.traces.timeout")).isNull(); - assertThat(configProperties.getString("otel.exporter.otlp.traces.certificate")).isNull(); - assertThat(configProperties.getString("otel.exporter.otlp.traces.client.key")).isNull(); - assertThat(configProperties.getString("otel.exporter.otlp.traces.client.certificate")).isNull(); + ArgumentCaptor configCaptor = + ArgumentCaptor.forClass(StructuredConfigProperties.class); + verify(spiHelper).loadComponent(eq(SpanExporter.class), eq("otlp"), configCaptor.capture()); + StructuredConfigProperties configProperties = configCaptor.getValue(); + assertThat(configProperties.getString("protocol")).isNull(); + assertThat(configProperties.getString("endpoint")).isNull(); + assertThat(configProperties.getStructured("headers")).isNull(); + assertThat(configProperties.getString("compression")).isNull(); + assertThat(configProperties.getInt("timeout")).isNull(); + assertThat(configProperties.getString("certificate")).isNull(); + assertThat(configProperties.getString("client_key")).isNull(); + assertThat(configProperties.getString("client_certificate")).isNull(); } @Test @@ -144,27 +149,22 @@ void create_OtlpConfigured(@TempDir Path tempDir) assertThat(exporter.toString()).isEqualTo(expectedExporter.toString()); - ArgumentCaptor configCaptor = ArgumentCaptor.forClass(ConfigProperties.class); - verify(spiHelper) - .loadConfigurable( - eq(ConfigurableSpanExporterProvider.class), any(), any(), configCaptor.capture()); - ConfigProperties configProperties = configCaptor.getValue(); - assertThat(configProperties.getString("otel.exporter.otlp.traces.protocol")) - .isEqualTo("http/protobuf"); - assertThat(configProperties.getString("otel.exporter.otlp.endpoint")) - .isEqualTo("http://example:4318"); - assertThat(configProperties.getMap("otel.exporter.otlp.traces.headers")) - .isEqualTo(ImmutableMap.of("key1", "value1", "key2", "value2")); - assertThat(configProperties.getString("otel.exporter.otlp.traces.compression")) - .isEqualTo("gzip"); - assertThat(configProperties.getDuration("otel.exporter.otlp.traces.timeout")) - .isEqualTo(Duration.ofSeconds(15)); - assertThat(configProperties.getString("otel.exporter.otlp.traces.certificate")) - .isEqualTo(certificatePath); - assertThat(configProperties.getString("otel.exporter.otlp.traces.client.key")) - .isEqualTo(clientKeyPath); - assertThat(configProperties.getString("otel.exporter.otlp.traces.client.certificate")) - .isEqualTo(clientCertificatePath); + ArgumentCaptor configCaptor = + ArgumentCaptor.forClass(StructuredConfigProperties.class); + verify(spiHelper).loadComponent(eq(SpanExporter.class), eq("otlp"), configCaptor.capture()); + StructuredConfigProperties configProperties = configCaptor.getValue(); + assertThat(configProperties.getString("protocol")).isEqualTo("http/protobuf"); + assertThat(configProperties.getString("endpoint")).isEqualTo("http://example:4318"); + StructuredConfigProperties headers = configProperties.getStructured("headers"); + assertThat(headers).isNotNull(); + assertThat(headers.getPropertyKeys()).isEqualTo(ImmutableSet.of("key1", "key2")); + assertThat(headers.getString("key1")).isEqualTo("value1"); + assertThat(headers.getString("key2")).isEqualTo("value2"); + assertThat(configProperties.getString("compression")).isEqualTo("gzip"); + assertThat(configProperties.getInt("timeout")).isEqualTo(Duration.ofSeconds(15).toMillis()); + assertThat(configProperties.getString("certificate")).isEqualTo(certificatePath); + assertThat(configProperties.getString("client_key")).isEqualTo(clientKeyPath); + assertThat(configProperties.getString("client_certificate")).isEqualTo(clientCertificatePath); } @Test @@ -209,13 +209,12 @@ void create_ZipkinDefaults() { assertThat(exporter.toString()).isEqualTo(expectedExporter.toString()); - ArgumentCaptor configCaptor = ArgumentCaptor.forClass(ConfigProperties.class); - verify(spiHelper) - .loadConfigurable( - eq(ConfigurableSpanExporterProvider.class), any(), any(), configCaptor.capture()); - ConfigProperties configProperties = configCaptor.getValue(); - assertThat(configProperties.getString("otel.exporter.zipkin.endpoint")).isNull(); - assertThat(configProperties.getDuration("otel.exporter.zipkin.timeout")).isNull(); + ArgumentCaptor configCaptor = + ArgumentCaptor.forClass(StructuredConfigProperties.class); + verify(spiHelper).loadComponent(eq(SpanExporter.class), eq("zipkin"), configCaptor.capture()); + StructuredConfigProperties configProperties = configCaptor.getValue(); + assertThat(configProperties.getString("endpoint")).isNull(); + assertThat(configProperties.getLong("timeout")).isNull(); } @Test @@ -245,19 +244,16 @@ void create_ZipkinConfigured() { assertThat(exporter.toString()).isEqualTo(expectedExporter.toString()); - ArgumentCaptor configCaptor = ArgumentCaptor.forClass(ConfigProperties.class); - verify(spiHelper) - .loadConfigurable( - eq(ConfigurableSpanExporterProvider.class), any(), any(), configCaptor.capture()); - ConfigProperties configProperties = configCaptor.getValue(); - assertThat(configProperties.getString("otel.exporter.zipkin.endpoint")) - .isEqualTo("http://zipkin:9411/v1/v2/spans"); - assertThat(configProperties.getDuration("otel.exporter.zipkin.timeout")) - .isEqualTo(Duration.ofSeconds(15)); + ArgumentCaptor configCaptor = + ArgumentCaptor.forClass(StructuredConfigProperties.class); + verify(spiHelper).loadComponent(eq(SpanExporter.class), eq("zipkin"), configCaptor.capture()); + StructuredConfigProperties configProperties = configCaptor.getValue(); + assertThat(configProperties.getString("endpoint")).isEqualTo("http://zipkin:9411/v1/v2/spans"); + assertThat(configProperties.getLong("timeout")).isEqualTo(15_000); } @Test - void create_SpiExporter() { + void create_SpiExporter_Unknown() { List closeables = new ArrayList<>(); assertThatThrownBy( @@ -266,11 +262,30 @@ void create_SpiExporter() { .create( new io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model .SpanExporter() - .withAdditionalProperty("test", ImmutableMap.of("key1", "value1")), + .withAdditionalProperty( + "unknown_key", ImmutableMap.of("key1", "value1")), spiHelper, new ArrayList<>())) .isInstanceOf(ConfigurationException.class) - .hasMessage("Unrecognized span exporter(s): [test]"); + .hasMessage( + "No component provider detected for io.opentelemetry.sdk.trace.export.SpanExporter with name \"unknown_key\"."); cleanup.addCloseables(closeables); } + + @Test + void create_SpiExporter_Valid() { + SpanExporter spanExporter = + SpanExporterFactory.getInstance() + .create( + new io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model + .SpanExporter() + .withAdditionalProperty("test", ImmutableMap.of("key1", "value1")), + spiHelper, + new ArrayList<>()); + assertThat(spanExporter).isInstanceOf(SpanExporterComponentProvider.TestSpanExporter.class); + assertThat( + ((SpanExporterComponentProvider.TestSpanExporter) spanExporter) + .config.getString("key1")) + .isEqualTo("value1"); + } } diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanProcessorFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanProcessorFactoryTest.java index 3c6059cc714..eb1fb1533ce 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanProcessorFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanProcessorFactoryTest.java @@ -48,19 +48,15 @@ void create_Null() { @Test void create_BatchNullExporter() { - List closeables = new ArrayList<>(); - - io.opentelemetry.sdk.trace.SpanProcessor processor = - SpanProcessorFactory.getInstance() - .create( - new SpanProcessor().withBatch(new BatchSpanProcessor()), - spiHelper, - Collections.emptyList()); - cleanup.addCloseable(processor); - cleanup.addCloseables(closeables); - - assertThat(processor.toString()) - .isEqualTo(io.opentelemetry.sdk.trace.SpanProcessor.composite().toString()); + assertThatThrownBy( + () -> + SpanProcessorFactory.getInstance() + .create( + new SpanProcessor().withBatch(new BatchSpanProcessor()), + spiHelper, + Collections.emptyList())) + .isInstanceOf(ConfigurationException.class) + .hasMessage("exporter required for batch span processor"); } @Test @@ -119,19 +115,15 @@ void create_BatchConfigured() { @Test void create_SimpleNullExporter() { - List closeables = new ArrayList<>(); - - io.opentelemetry.sdk.trace.SpanProcessor processor = - SpanProcessorFactory.getInstance() - .create( - new SpanProcessor().withSimple(new SimpleSpanProcessor()), - spiHelper, - Collections.emptyList()); - cleanup.addCloseable(processor); - cleanup.addCloseables(closeables); - - assertThat(processor.toString()) - .isEqualTo(io.opentelemetry.sdk.trace.SpanProcessor.composite().toString()); + assertThatThrownBy( + () -> + SpanProcessorFactory.getInstance() + .create( + new SpanProcessor().withSimple(new SimpleSpanProcessor()), + spiHelper, + Collections.emptyList())) + .isInstanceOf(ConfigurationException.class) + .hasMessage("exporter required for simple span processor"); } @Test diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/component/LogRecordExporterComponentProvider.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/component/LogRecordExporterComponentProvider.java new file mode 100644 index 00000000000..2f2d6f56c7c --- /dev/null +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/component/LogRecordExporterComponentProvider.java @@ -0,0 +1,54 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig.component; + +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.logs.export.LogRecordExporter; +import java.util.Collection; + +public class LogRecordExporterComponentProvider implements ComponentProvider { + @Override + public Class getType() { + return LogRecordExporter.class; + } + + @Override + public String getName() { + return "test"; + } + + @Override + public LogRecordExporter create(StructuredConfigProperties config) { + return new TestLogRecordExporter(config); + } + + public static class TestLogRecordExporter implements LogRecordExporter { + + public final StructuredConfigProperties config; + + private TestLogRecordExporter(StructuredConfigProperties config) { + this.config = config; + } + + @Override + public CompletableResultCode export(Collection logs) { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode flush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + return CompletableResultCode.ofSuccess(); + } + } +} diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/component/MetricExporterComponentProvider.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/component/MetricExporterComponentProvider.java new file mode 100644 index 00000000000..b0c46a8be92 --- /dev/null +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/component/MetricExporterComponentProvider.java @@ -0,0 +1,63 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig.component; + +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.metrics.InstrumentType; +import io.opentelemetry.sdk.metrics.data.AggregationTemporality; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.export.AggregationTemporalitySelector; +import io.opentelemetry.sdk.metrics.export.MetricExporter; +import java.util.Collection; + +public class MetricExporterComponentProvider implements ComponentProvider { + @Override + public Class getType() { + return MetricExporter.class; + } + + @Override + public String getName() { + return "test"; + } + + @Override + public MetricExporter create(StructuredConfigProperties config) { + return new TestMetricExporter(config); + } + + public static class TestMetricExporter implements MetricExporter { + + public final StructuredConfigProperties config; + + private TestMetricExporter(StructuredConfigProperties config) { + this.config = config; + } + + @Override + public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) { + return AggregationTemporalitySelector.alwaysCumulative() + .getAggregationTemporality(instrumentType); + } + + @Override + public CompletableResultCode export(Collection metrics) { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode flush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + return CompletableResultCode.ofSuccess(); + } + } +} diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/component/SpanExporterComponentProvider.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/component/SpanExporterComponentProvider.java new file mode 100644 index 00000000000..f387454f0fd --- /dev/null +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/component/SpanExporterComponentProvider.java @@ -0,0 +1,54 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig.component; + +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import java.util.Collection; + +public class SpanExporterComponentProvider implements ComponentProvider { + @Override + public Class getType() { + return SpanExporter.class; + } + + @Override + public String getName() { + return "test"; + } + + @Override + public SpanExporter create(StructuredConfigProperties config) { + return new TestSpanExporter(config); + } + + public static class TestSpanExporter implements SpanExporter { + + public final StructuredConfigProperties config; + + private TestSpanExporter(StructuredConfigProperties config) { + this.config = config; + } + + @Override + public CompletableResultCode export(Collection spans) { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode flush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + return CompletableResultCode.ofSuccess(); + } + } +} diff --git a/sdk-extensions/incubator/src/test/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider b/sdk-extensions/incubator/src/test/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider new file mode 100644 index 00000000000..0dc2d209e1d --- /dev/null +++ b/sdk-extensions/incubator/src/test/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider @@ -0,0 +1,3 @@ +io.opentelemetry.sdk.extension.incubator.fileconfig.component.MetricExporterComponentProvider +io.opentelemetry.sdk.extension.incubator.fileconfig.component.SpanExporterComponentProvider +io.opentelemetry.sdk.extension.incubator.fileconfig.component.LogRecordExporterComponentProvider From fa99b778084f60020c37d1813f4db0a42b3c2d9f Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Mon, 15 Jul 2024 10:08:24 -0500 Subject: [PATCH 2/2] Improve OtlpConfigUtil error messages --- .../exporter/otlp/internal/OtlpConfigUtil.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpConfigUtil.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpConfigUtil.java index 693caa69d29..b9c46a2bb55 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpConfigUtil.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpConfigUtil.java @@ -140,9 +140,11 @@ public static void configureOtlpExporterBuilder( determinePropertyByType(config, "otel.exporter.otlp", dataType, "client.certificate")); if (clientKeyPath != null && clientKeyChainPath == null) { - throw new ConfigurationException("Client key provided but certification chain is missing"); + throw new ConfigurationException( + "client key provided without client certificate - both client key and client certificate must be set"); } else if (clientKeyPath == null && clientKeyChainPath != null) { - throw new ConfigurationException("Client key chain provided but key is missing"); + throw new ConfigurationException( + "client certificate provided without client key - both client key and client_certificate must be set"); } byte[] certificateBytes = readFileBytes(certificatePath); @@ -222,9 +224,11 @@ public static void configureOtlpExporterBuilder( String clientKeyChainPath = config.getString("client_certificate"); if (clientKeyPath != null && clientKeyChainPath == null) { - throw new ConfigurationException("client_key provided but client_certificate"); + throw new ConfigurationException( + "client_key provided without client_certificate - both client_key and client_certificate must be set"); } else if (clientKeyPath == null && clientKeyChainPath != null) { - throw new ConfigurationException("client_certificate provided but client_key"); + throw new ConfigurationException( + "client_certificate provided without client_key - both client_key and client_certificate must be set"); } byte[] certificateBytes = readFileBytes(certificatePath); if (certificateBytes != null) {