diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging.txt b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging.txt index df26146497..ec040faaf8 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-logging.txt @@ -1,2 +1,10 @@ Comparing source compatibility of against -No changes. \ No newline at end of file +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.exporter.logging.LoggingMetricExporter (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) java.lang.String toString() +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.exporter.logging.LoggingSpanExporter (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) java.lang.String toString() +*** MODIFIED CLASS: PUBLIC io.opentelemetry.exporter.logging.SystemOutLogRecordExporter (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) java.lang.String toString() diff --git a/exporters/logging/src/main/java/io/opentelemetry/exporter/logging/LoggingMetricExporter.java b/exporters/logging/src/main/java/io/opentelemetry/exporter/logging/LoggingMetricExporter.java index f3cf842102..8b7d837330 100644 --- a/exporters/logging/src/main/java/io/opentelemetry/exporter/logging/LoggingMetricExporter.java +++ b/exporters/logging/src/main/java/io/opentelemetry/exporter/logging/LoggingMetricExporter.java @@ -103,4 +103,9 @@ public final class LoggingMetricExporter implements MetricExporter { } return flush(); } + + @Override + public String toString() { + return "LoggingMetricExporter{}"; + } } diff --git a/exporters/logging/src/main/java/io/opentelemetry/exporter/logging/LoggingSpanExporter.java b/exporters/logging/src/main/java/io/opentelemetry/exporter/logging/LoggingSpanExporter.java index 3d19f9e9ee..58c828635c 100644 --- a/exporters/logging/src/main/java/io/opentelemetry/exporter/logging/LoggingSpanExporter.java +++ b/exporters/logging/src/main/java/io/opentelemetry/exporter/logging/LoggingSpanExporter.java @@ -93,4 +93,9 @@ public final class LoggingSpanExporter implements SpanExporter { } return flush(); } + + @Override + public String toString() { + return "LoggingSpanExporter{}"; + } } diff --git a/exporters/logging/src/main/java/io/opentelemetry/exporter/logging/SystemOutLogRecordExporter.java b/exporters/logging/src/main/java/io/opentelemetry/exporter/logging/SystemOutLogRecordExporter.java index e5e3b0d4f1..6655c12453 100644 --- a/exporters/logging/src/main/java/io/opentelemetry/exporter/logging/SystemOutLogRecordExporter.java +++ b/exporters/logging/src/main/java/io/opentelemetry/exporter/logging/SystemOutLogRecordExporter.java @@ -95,4 +95,9 @@ public class SystemOutLogRecordExporter implements LogRecordExporter { } return CompletableResultCode.ofSuccess(); } + + @Override + public String toString() { + return "SystemOutLogRecordExporter{}"; + } } diff --git a/exporters/logging/src/test/java/io/opentelemetry/exporter/logging/LoggingMetricExporterTest.java b/exporters/logging/src/test/java/io/opentelemetry/exporter/logging/LoggingMetricExporterTest.java index f7ab6fa38b..a3021dc8a6 100644 --- a/exporters/logging/src/test/java/io/opentelemetry/exporter/logging/LoggingMetricExporterTest.java +++ b/exporters/logging/src/test/java/io/opentelemetry/exporter/logging/LoggingMetricExporterTest.java @@ -114,4 +114,9 @@ class LoggingMetricExporterTest { assertThat(exporter.shutdown().isSuccess()).isTrue(); logs.assertContains("Calling shutdown() multiple times."); } + + @Test + void stringRepresentation() { + assertThat(LoggingMetricExporter.create().toString()).isEqualTo("LoggingMetricExporter{}"); + } } diff --git a/exporters/logging/src/test/java/io/opentelemetry/exporter/logging/LoggingSpanExporterTest.java b/exporters/logging/src/test/java/io/opentelemetry/exporter/logging/LoggingSpanExporterTest.java index 3e152f89e5..385784732f 100644 --- a/exporters/logging/src/test/java/io/opentelemetry/exporter/logging/LoggingSpanExporterTest.java +++ b/exporters/logging/src/test/java/io/opentelemetry/exporter/logging/LoggingSpanExporterTest.java @@ -139,4 +139,9 @@ class LoggingSpanExporterTest { assertThat(exporter.shutdown().isSuccess()).isTrue(); logs.assertContains("Calling shutdown() multiple times."); } + + @Test + void stringRepresentation() { + assertThat(LoggingSpanExporter.create().toString()).isEqualTo("LoggingSpanExporter{}"); + } } diff --git a/exporters/logging/src/test/java/io/opentelemetry/exporter/logging/SystemOutLogRecordExporterTest.java b/exporters/logging/src/test/java/io/opentelemetry/exporter/logging/SystemOutLogRecordExporterTest.java index 2c06e2d098..2cf9b22f5c 100644 --- a/exporters/logging/src/test/java/io/opentelemetry/exporter/logging/SystemOutLogRecordExporterTest.java +++ b/exporters/logging/src/test/java/io/opentelemetry/exporter/logging/SystemOutLogRecordExporterTest.java @@ -61,6 +61,12 @@ class SystemOutLogRecordExporterTest { assertThat(exporter.shutdown().isSuccess()).isTrue(); } + @Test + void stringRepresentation() { + assertThat(SystemOutLogRecordExporter.create().toString()) + .isEqualTo("SystemOutLogRecordExporter{}"); + } + private static LogRecordData sampleLog(long timestamp) { return TestLogRecordData.builder() .setResource(Resource.empty()) diff --git a/sdk-extensions/incubator/build.gradle.kts b/sdk-extensions/incubator/build.gradle.kts index c4d16951db..01bb56f449 100644 --- a/sdk-extensions/incubator/build.gradle.kts +++ b/sdk-extensions/incubator/build.gradle.kts @@ -33,6 +33,8 @@ dependencies { testImplementation(project(":sdk:testing")) testImplementation(project(":sdk-extensions:autoconfigure")) testImplementation(project(":exporters:otlp:all")) + testImplementation(project(":exporters:prometheus")) + testImplementation(project(":exporters:logging")) testImplementation(project(":sdk-extensions:jaeger-remote-sampler")) testImplementation(project(":extensions:trace-propagators")) // As a part of the tests we check that we can parse examples without error. The https://github.com/open-telemetry/opentelemetry-configuration/blob/main/examples/kitchen-sink.yam contains a reference to the xray propagator diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactory.java new file mode 100644 index 0000000000..726177d65a --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactory.java @@ -0,0 +1,77 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig; + +import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Aggregation; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Base2ExponentialBucketHistogram; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExplicitBucketHistogram; +import java.io.Closeable; +import java.util.List; +import javax.annotation.Nullable; + +final class AggregationFactory + implements Factory { + + private static final AggregationFactory INSTANCE = new AggregationFactory(); + + private AggregationFactory() {} + + static AggregationFactory getInstance() { + return INSTANCE; + } + + @Override + public io.opentelemetry.sdk.metrics.Aggregation create( + @Nullable Aggregation model, SpiHelper spiHelper, List closeables) { + if (model == null) { + return io.opentelemetry.sdk.metrics.Aggregation.defaultAggregation(); + } + + if (model.getDrop() != null) { + return io.opentelemetry.sdk.metrics.Aggregation.drop(); + } + if (model.getSum() != null) { + return io.opentelemetry.sdk.metrics.Aggregation.sum(); + } + if (model.getLastValue() != null) { + return io.opentelemetry.sdk.metrics.Aggregation.lastValue(); + } + Base2ExponentialBucketHistogram exponentialBucketHistogram = + model.getBase2ExponentialBucketHistogram(); + if (exponentialBucketHistogram != null) { + Integer maxScale = exponentialBucketHistogram.getMaxScale(); + if (maxScale == null) { + maxScale = 20; + } + Integer maxSize = exponentialBucketHistogram.getMaxSize(); + if (maxSize == null) { + maxSize = 160; + } + try { + return io.opentelemetry.sdk.metrics.Aggregation.base2ExponentialBucketHistogram( + maxSize, maxScale); + } catch (IllegalArgumentException e) { + throw new ConfigurationException("Invalid exponential bucket histogram", e); + } + } + ExplicitBucketHistogram explicitBucketHistogram = model.getExplicitBucketHistogram(); + if (explicitBucketHistogram != null) { + List boundaries = explicitBucketHistogram.getBoundaries(); + if (boundaries == null) { + return io.opentelemetry.sdk.metrics.Aggregation.explicitBucketHistogram(); + } + try { + return io.opentelemetry.sdk.metrics.Aggregation.explicitBucketHistogram(boundaries); + } catch (IllegalArgumentException e) { + throw new ConfigurationException("Invalid explicit bucket histogram", e); + } + } + + return io.opentelemetry.sdk.metrics.Aggregation.defaultAggregation(); + } +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/InstrumentSelectorFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/InstrumentSelectorFactory.java new file mode 100644 index 0000000000..ec117228b6 --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/InstrumentSelectorFactory.java @@ -0,0 +1,65 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig; + +import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Selector; +import io.opentelemetry.sdk.metrics.InstrumentSelector; +import io.opentelemetry.sdk.metrics.InstrumentSelectorBuilder; +import io.opentelemetry.sdk.metrics.InstrumentType; +import java.io.Closeable; +import java.util.List; +import javax.annotation.Nullable; + +final class InstrumentSelectorFactory implements Factory { + + private static final InstrumentSelectorFactory INSTANCE = new InstrumentSelectorFactory(); + + private InstrumentSelectorFactory() {} + + static InstrumentSelectorFactory getInstance() { + return INSTANCE; + } + + @Override + public InstrumentSelector create( + @Nullable Selector model, SpiHelper spiHelper, List closeables) { + if (model == null) { + throw new ConfigurationException("selector must not be null"); + } + + InstrumentSelectorBuilder builder = InstrumentSelector.builder(); + if (model.getInstrumentName() != null) { + builder.setName(model.getInstrumentName()); + } + if (model.getInstrumentType() != null) { + InstrumentType instrumentType; + try { + instrumentType = InstrumentType.valueOf(model.getInstrumentType().name()); + } catch (IllegalArgumentException e) { + throw new ConfigurationException( + "Unrecognized instrument type: " + model.getInstrumentType(), e); + } + builder.setType(instrumentType); + } + if (model.getMeterName() != null) { + builder.setMeterName(model.getMeterName()); + } + if (model.getMeterSchemaUrl() != null) { + builder.setMeterSchemaUrl(model.getMeterSchemaUrl()); + } + if (model.getMeterVersion() != null) { + builder.setMeterVersion(model.getMeterVersion()); + } + + try { + return builder.build(); + } catch (IllegalArgumentException e) { + throw new ConfigurationException("Invalid selector", e); + } + } +} 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 5cfc9897dd..3906fa3a88 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 @@ -45,53 +45,9 @@ final class LogRecordExporterFactory return LogRecordExporter.composite(); } - if (model.getOtlp() != null) { - Otlp otlp = model.getOtlp(); - - // 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()); - } - - // TODO(jack-berg): add method for creating from map - ConfigProperties configProperties = DefaultConfigProperties.createForTest(properties); - - return FileConfigUtil.addAndReturn( - closeables, - FileConfigUtil.assertNotNull( - logRecordExporterSpiManager(configProperties, spiHelper).getByName("otlp"), - "otlp exporter")); + Otlp otlpModel = model.getOtlp(); + if (otlpModel != null) { + return FileConfigUtil.addAndReturn(closeables, createOtlpExporter(otlpModel, spiHelper)); } // TODO(jack-berg): add support for generic SPI exporters @@ -104,6 +60,51 @@ final class LogRecordExporterFactory 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()); + } + + // TODO(jack-berg): add method for creating from map + ConfigProperties configProperties = DefaultConfigProperties.createForTest(properties); + + return FileConfigUtil.assertNotNull( + logRecordExporterSpiManager(configProperties, spiHelper).getByName("otlp"), + "otlp exporter"); + } + private static NamedSpiManager logRecordExporterSpiManager( ConfigProperties config, SpiHelper spiHelper) { return spiHelper.loadConfigurable( diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MeterProviderFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MeterProviderFactory.java new file mode 100644 index 0000000000..0acb6c703f --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MeterProviderFactory.java @@ -0,0 +1,61 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig; + +import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MeterProvider; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricReader; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.View; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; +import java.io.Closeable; +import java.util.List; +import javax.annotation.Nullable; + +final class MeterProviderFactory implements Factory { + + private static final MeterProviderFactory INSTANCE = new MeterProviderFactory(); + + private MeterProviderFactory() {} + + static MeterProviderFactory getInstance() { + return INSTANCE; + } + + @Override + public SdkMeterProviderBuilder create( + @Nullable MeterProvider model, SpiHelper spiHelper, List closeables) { + if (model == null) { + return SdkMeterProvider.builder(); + } + + SdkMeterProviderBuilder builder = SdkMeterProvider.builder(); + + List readerModels = model.getReaders(); + if (readerModels != null) { + readerModels.forEach( + readerModel -> { + io.opentelemetry.sdk.metrics.export.MetricReader metricReader = + MetricReaderFactory.getInstance().create(readerModel, spiHelper, closeables); + if (metricReader != null) { + builder.registerMetricReader(metricReader); + } + }); + } + + List viewModels = model.getViews(); + if (viewModels != null) { + viewModels.forEach( + viewModel -> + builder.registerView( + InstrumentSelectorFactory.getInstance() + .create(viewModel.getSelector(), spiHelper, closeables), + ViewFactory.getInstance().create(viewModel.getStream(), spiHelper, closeables))); + } + + return builder; + } +} 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 new file mode 100644 index 0000000000..c8c905e371 --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricExporterFactory.java @@ -0,0 +1,142 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig; + +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; + +final class MetricExporterFactory + implements Factory< + io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricExporter, + MetricExporter> { + + private static final MetricExporterFactory INSTANCE = new MetricExporterFactory(); + + private MetricExporterFactory() {} + + static MetricExporterFactory getInstance() { + return INSTANCE; + } + + @SuppressWarnings("NullAway") // Override superclass non-null response + @Override + @Nullable + public MetricExporter create( + @Nullable + io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricExporter model, + SpiHelper spiHelper, + List closeables) { + if (model == null) { + return null; + } + + OtlpMetric otlpModel = model.getOtlp(); + if (otlpModel != null) { + return FileConfigUtil.addAndReturn(closeables, createOtlpExporter(otlpModel, spiHelper)); + } + + if (model.getConsole() != null) { + return FileConfigUtil.addAndReturn(closeables, createConsoleExporter(spiHelper)); + } + + 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(",", "[", "]"))); + } + + 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()); + } + + // TODO(jack-berg): add method for creating from map + ConfigProperties configProperties = DefaultConfigProperties.createForTest(properties); + return FileConfigUtil.assertNotNull( + metricExporterSpiManager(configProperties, spiHelper).getByName("otlp"), "otlp exporter"); + } + + private static MetricExporter createConsoleExporter(SpiHelper spiHelper) { + return FileConfigUtil.assertNotNull( + metricExporterSpiManager( + DefaultConfigProperties.createForTest(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 new file mode 100644 index 0000000000..7f54c7c1e9 --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactory.java @@ -0,0 +1,117 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig; + +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.ConfigurableMetricReaderProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricExporter; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PeriodicMetricReader; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Prometheus; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PullMetricReader; +import io.opentelemetry.sdk.metrics.export.MetricReader; +import io.opentelemetry.sdk.metrics.export.PeriodicMetricReaderBuilder; +import java.io.Closeable; +import java.time.Duration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; + +final class MetricReaderFactory + implements Factory< + io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricReader, + MetricReader> { + + private static final MetricReaderFactory INSTANCE = new MetricReaderFactory(); + + private MetricReaderFactory() {} + + static MetricReaderFactory getInstance() { + return INSTANCE; + } + + @SuppressWarnings("NullAway") // Override superclass non-null response + @Override + @Nullable + public MetricReader create( + @Nullable + io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricReader model, + SpiHelper spiHelper, + List closeables) { + if (model == null) { + return null; + } + + 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; + } + PeriodicMetricReaderBuilder builder = + io.opentelemetry.sdk.metrics.export.PeriodicMetricReader.builder( + FileConfigUtil.addAndReturn(closeables, metricExporter)); + if (periodicModel.getInterval() != null) { + builder.setInterval(Duration.ofMillis(periodicModel.getInterval())); + } + return FileConfigUtil.addAndReturn(closeables, builder.build()); + } + + PullMetricReader pullModel = model.getPull(); + if (pullModel != null) { + MetricExporter exporterModel = pullModel.getExporter(); + if (exporterModel == null) { + throw new ConfigurationException("exporter required for pull reader"); + } + Prometheus prometheusModel = exporterModel.getPrometheus(); + if (prometheusModel != null) { + // Translate from file configuration scheme to environment variable scheme. This is + // ultimately + // interpreted by PrometheusMetricReaderProvider, but we want to avoid the dependency on + // opentelemetry-exporter-prometheus + Map properties = new HashMap<>(); + if (prometheusModel.getHost() != null) { + properties.put("otel.exporter.prometheus.host", prometheusModel.getHost()); + } + if (prometheusModel.getPort() != null) { + properties.put( + "otel.exporter.prometheus.port", String.valueOf(prometheusModel.getPort())); + } + + // TODO(jack-berg): add method for creating from map + ConfigProperties configProperties = DefaultConfigProperties.createForTest(properties); + + return FileConfigUtil.addAndReturn( + closeables, + FileConfigUtil.assertNotNull( + metricReaderSpiManager(configProperties, spiHelper).getByName("prometheus"), + "prometheus reader")); + } + + throw new ConfigurationException("prometheus is the only currently supported pull reader"); + } + + return null; + } + + private static NamedSpiManager + metricReaderSpiManager(ConfigProperties config, SpiHelper spiHelper) { + return spiHelper.loadConfigurable( + ConfigurableMetricReaderProvider.class, + ConfigurableMetricReaderProvider::getName, + ConfigurableMetricReaderProvider::createMetricReader, + config); + } +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java index 7f2ea412ff..cb6041c7e2 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java @@ -71,7 +71,16 @@ final class OpenTelemetryConfigurationFactory .build())); } - // TODO(jack-berg): add support for meter provider + if (model.getMeterProvider() != null) { + builder.setMeterProvider( + FileConfigUtil.addAndReturn( + closeables, + MeterProviderFactory.getInstance() + .create(model.getMeterProvider(), spiHelper, closeables) + .setResource(resource) + .build())); + } + // TODO(jack-berg): add support for general attribute limits return FileConfigUtil.addAndReturn(closeables, builder.build()); 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 2b303f4339..963fe90cae 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 @@ -16,6 +16,7 @@ import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterPro import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Otlp; 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; @@ -44,53 +45,19 @@ final class SpanExporterFactory return SpanExporter.composite(); } - if (model.getOtlp() != null) { - Otlp otlp = model.getOtlp(); - - // 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.traces.protocol", otlp.getProtocol()); - } - if (otlp.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", otlp.getEndpoint()); - } - if (otlp.getHeaders() != null) { - properties.put( - "otel.exporter.otlp.traces.headers", - otlp.getHeaders().getAdditionalProperties().entrySet().stream() - .map(entry -> entry.getKey() + "=" + entry.getValue()) - .collect(joining(","))); - } - if (otlp.getCompression() != null) { - properties.put("otel.exporter.otlp.traces.compression", otlp.getCompression()); - } - if (otlp.getTimeout() != null) { - properties.put("otel.exporter.otlp.traces.timeout", Integer.toString(otlp.getTimeout())); - } - if (otlp.getCertificate() != null) { - properties.put("otel.exporter.otlp.traces.certificate", otlp.getCertificate()); - } - if (otlp.getClientKey() != null) { - properties.put("otel.exporter.otlp.traces.client.key", otlp.getClientKey()); - } - if (otlp.getClientCertificate() != null) { - properties.put("otel.exporter.otlp.traces.client.certificate", otlp.getClientCertificate()); - } - - // TODO(jack-berg): add method for creating from map - ConfigProperties configProperties = DefaultConfigProperties.createForTest(properties); + Otlp otlpModel = model.getOtlp(); + if (otlpModel != null) { + return FileConfigUtil.addAndReturn(closeables, createOtlpExporter(otlpModel, spiHelper)); + } + if (model.getConsole() != null) { return FileConfigUtil.addAndReturn( closeables, FileConfigUtil.assertNotNull( - spanExporterSpiManager(configProperties, spiHelper).getByName("otlp"), - "otlp exporter")); + spanExporterSpiManager( + DefaultConfigProperties.createForTest(Collections.emptyMap()), spiHelper) + .getByName("logging"), + "logging exporter")); } // TODO(jack-berg): add support for generic SPI exporters @@ -103,6 +70,50 @@ final class SpanExporterFactory 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()); + } + + // TODO(jack-berg): add method for creating from map + ConfigProperties configProperties = DefaultConfigProperties.createForTest(properties); + + return FileConfigUtil.assertNotNull( + spanExporterSpiManager(configProperties, spiHelper).getByName("otlp"), "otlp exporter"); + } + private static NamedSpiManager spanExporterSpiManager( ConfigProperties config, SpiHelper spiHelper) { return spiHelper.loadConfigurable( diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ViewFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ViewFactory.java new file mode 100644 index 0000000000..77a6683376 --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ViewFactory.java @@ -0,0 +1,50 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig; + +import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Stream; +import io.opentelemetry.sdk.metrics.View; +import io.opentelemetry.sdk.metrics.ViewBuilder; +import java.io.Closeable; +import java.util.HashSet; +import java.util.List; +import javax.annotation.Nullable; + +final class ViewFactory implements Factory { + + private static final ViewFactory INSTANCE = new ViewFactory(); + + private ViewFactory() {} + + static ViewFactory getInstance() { + return INSTANCE; + } + + @Override + public View create(@Nullable Stream model, SpiHelper spiHelper, List closeables) { + if (model == null) { + throw new ConfigurationException("stream must not be null"); + } + + ViewBuilder builder = View.builder(); + if (model.getName() != null) { + builder.setName(model.getName()); + } + if (model.getDescription() != null) { + builder.setDescription(model.getDescription()); + } + if (model.getAttributeKeys() != null) { + builder.setAttributeFilter(new HashSet<>(model.getAttributeKeys())); + } + if (model.getAggregation() != null) { + builder.setAggregation( + AggregationFactory.getInstance().create(model.getAggregation(), spiHelper, closeables)); + } + return builder.build(); + } +} diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactoryTest.java new file mode 100644 index 0000000000..3b37e2077a --- /dev/null +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactoryTest.java @@ -0,0 +1,76 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static org.mockito.Mockito.mock; + +import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Aggregation; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Base2ExponentialBucketHistogram; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExplicitBucketHistogram; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class AggregationFactoryTest { + + @Test + void create_Null() { + assertThat( + AggregationFactory.getInstance() + .create(null, mock(SpiHelper.class), Collections.emptyList()) + .toString()) + .isEqualTo(io.opentelemetry.sdk.metrics.Aggregation.defaultAggregation().toString()); + } + + @ParameterizedTest + @MethodSource("createTestCases") + void create(Aggregation model, io.opentelemetry.sdk.metrics.Aggregation expectedResult) { + io.opentelemetry.sdk.metrics.Aggregation aggregation = + AggregationFactory.getInstance().create(model, mock(SpiHelper.class), new ArrayList<>()); + assertThat(aggregation.toString()).isEqualTo(expectedResult.toString()); + } + + private static Stream createTestCases() { + return Stream.of( + Arguments.of( + new Aggregation(), io.opentelemetry.sdk.metrics.Aggregation.defaultAggregation()), + Arguments.of( + new Aggregation().withDrop(new Object()), + io.opentelemetry.sdk.metrics.Aggregation.drop()), + Arguments.of( + new Aggregation().withSum(new Object()), + io.opentelemetry.sdk.metrics.Aggregation.sum()), + Arguments.of( + new Aggregation().withLastValue(new Object()), + io.opentelemetry.sdk.metrics.Aggregation.lastValue()), + Arguments.of( + new Aggregation() + .withBase2ExponentialBucketHistogram(new Base2ExponentialBucketHistogram()), + io.opentelemetry.sdk.metrics.Aggregation.base2ExponentialBucketHistogram()), + Arguments.of( + new Aggregation() + .withBase2ExponentialBucketHistogram( + new Base2ExponentialBucketHistogram().withMaxSize(1).withMaxScale(2)), + io.opentelemetry.sdk.metrics.Aggregation.base2ExponentialBucketHistogram(1, 2)), + Arguments.of( + new Aggregation() + .withExplicitBucketHistogram(new ExplicitBucketHistogram().withBoundaries(null)), + io.opentelemetry.sdk.metrics.Aggregation.explicitBucketHistogram()), + Arguments.of( + new Aggregation() + .withExplicitBucketHistogram( + new ExplicitBucketHistogram().withBoundaries(Arrays.asList(1.0, 2.0))), + io.opentelemetry.sdk.metrics.Aggregation.explicitBucketHistogram( + Arrays.asList(1.0, 2.0)))); + } +} diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/InstrumentSelectorFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/InstrumentSelectorFactoryTest.java new file mode 100644 index 0000000000..c10b6394bc --- /dev/null +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/InstrumentSelectorFactoryTest.java @@ -0,0 +1,64 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; + +import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Selector; +import io.opentelemetry.sdk.metrics.InstrumentSelector; +import io.opentelemetry.sdk.metrics.InstrumentType; +import java.util.Collections; +import org.junit.jupiter.api.Test; + +class InstrumentSelectorFactoryTest { + + @Test + void create_Null() { + assertThatThrownBy( + () -> + InstrumentSelectorFactory.getInstance() + .create(null, mock(SpiHelper.class), Collections.emptyList())) + .isInstanceOf(ConfigurationException.class) + .hasMessage("selector must not be null"); + } + + @Test + void create_Defaults() { + assertThatThrownBy( + () -> + InstrumentSelectorFactory.getInstance() + .create(new Selector(), mock(SpiHelper.class), Collections.emptyList())) + .isInstanceOf(ConfigurationException.class) + .hasMessage("Invalid selector"); + } + + @Test + void create() { + assertThat( + InstrumentSelectorFactory.getInstance() + .create( + new Selector() + .withInstrumentName("instrument-name") + .withInstrumentType(Selector.InstrumentType.COUNTER) + .withMeterName("meter-name") + .withMeterSchemaUrl("https://opentelemetry.io/schemas/1.16.0") + .withMeterVersion("1.0.0"), + mock(SpiHelper.class), + Collections.emptyList())) + .isEqualTo( + InstrumentSelector.builder() + .setName("instrument-name") + .setType(InstrumentType.COUNTER) + .setMeterName("meter-name") + .setMeterSchemaUrl("https://opentelemetry.io/schemas/1.16.0") + .setMeterVersion("1.0.0") + .build()); + } +} 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 655ad41596..721421c855 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 @@ -56,6 +56,16 @@ class LogRecordExporterFactoryTest { private SpiHelper spiHelper = SpiHelper.create(LogRecordExporterFactoryTest.class.getClassLoader()); + @Test + void create_Null() { + LogRecordExporter expectedExporter = LogRecordExporter.composite(); + + LogRecordExporter exporter = + LogRecordExporterFactory.getInstance().create(null, spiHelper, new ArrayList<>()); + + assertThat(exporter.toString()).isEqualTo(expectedExporter.toString()); + } + @Test void create_OtlpDefaults() { spiHelper = spy(spiHelper); diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MeterProviderFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MeterProviderFactoryTest.java new file mode 100644 index 0000000000..bdcc24626a --- /dev/null +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MeterProviderFactoryTest.java @@ -0,0 +1,108 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; + +import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; +import io.opentelemetry.internal.testing.CleanupExtension; +import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MeterProvider; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricExporter; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricReader; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OtlpMetric; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PeriodicMetricReader; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Selector; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Stream; +import io.opentelemetry.sdk.metrics.InstrumentSelector; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.View; +import java.io.Closeable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +class MeterProviderFactoryTest { + + @RegisterExtension CleanupExtension cleanup = new CleanupExtension(); + + private final SpiHelper spiHelper = + SpiHelper.create(MeterProviderFactoryTest.class.getClassLoader()); + + @Test + void create_Null() { + List closeables = new ArrayList<>(); + SdkMeterProvider expectedProvider = SdkMeterProvider.builder().build(); + cleanup.addCloseable(expectedProvider); + + SdkMeterProvider provider = + MeterProviderFactory.getInstance().create(null, spiHelper, closeables).build(); + cleanup.addCloseable(provider); + cleanup.addCloseables(closeables); + + assertThat(provider.toString()).isEqualTo(expectedProvider.toString()); + } + + @Test + void create_Defaults() { + List closeables = new ArrayList<>(); + SdkMeterProvider expectedProvider = SdkMeterProvider.builder().build(); + cleanup.addCloseable(expectedProvider); + + SdkMeterProvider provider = + MeterProviderFactory.getInstance() + .create(new MeterProvider(), spiHelper, closeables) + .build(); + cleanup.addCloseable(provider); + cleanup.addCloseables(closeables); + + assertThat(provider.toString()).isEqualTo(expectedProvider.toString()); + } + + @Test + void create_Configured() { + List closeables = new ArrayList<>(); + SdkMeterProvider expectedProvider = + SdkMeterProvider.builder() + .registerMetricReader( + io.opentelemetry.sdk.metrics.export.PeriodicMetricReader.builder( + OtlpGrpcMetricExporter.getDefault()) + .build()) + .registerView( + InstrumentSelector.builder().setName("instrument-name").build(), + View.builder().setName("stream-name").build()) + .build(); + cleanup.addCloseable(expectedProvider); + + SdkMeterProvider provider = + MeterProviderFactory.getInstance() + .create( + new MeterProvider() + .withReaders( + Collections.singletonList( + new MetricReader() + .withPeriodic( + new PeriodicMetricReader() + .withExporter( + new MetricExporter().withOtlp(new OtlpMetric()))))) + .withViews( + Collections.singletonList( + new io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model + .View() + .withSelector(new Selector().withInstrumentName("instrument-name")) + .withStream( + new Stream().withName("stream-name").withAttributeKeys(null)))), + spiHelper, + closeables) + .build(); + cleanup.addCloseable(provider); + cleanup.addCloseables(closeables); + + assertThat(provider.toString()).isEqualTo(expectedProvider.toString()); + } +} 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 new file mode 100644 index 0000000000..4b9b752044 --- /dev/null +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricExporterFactoryTest.java @@ -0,0 +1,246 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig; + +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.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.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; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OtlpMetric.DefaultHistogramAggregation; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Prometheus; +import io.opentelemetry.sdk.metrics.export.MetricExporter; +import java.io.Closeable; +import java.io.IOException; +import java.nio.file.Path; +import java.security.cert.CertificateEncodingException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.ArgumentCaptor; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class MetricExporterFactoryTest { + + @RegisterExtension + static final SelfSignedCertificateExtension serverTls = new SelfSignedCertificateExtension(); + + @RegisterExtension + static final SelfSignedCertificateExtension clientTls = new SelfSignedCertificateExtension(); + + @RegisterExtension CleanupExtension cleanup = new CleanupExtension(); + + private SpiHelper spiHelper = SpiHelper.create(MetricExporterFactoryTest.class.getClassLoader()); + + @Test + void create_Null() { + assertThat(MetricExporterFactory.getInstance().create(null, spiHelper, new ArrayList<>())) + .isNull(); + } + + @Test + void create_OtlpDefaults() { + spiHelper = spy(spiHelper); + List closeables = new ArrayList<>(); + OtlpGrpcMetricExporter expectedExporter = OtlpGrpcMetricExporter.getDefault(); + cleanup.addCloseable(expectedExporter); + + MetricExporter exporter = + MetricExporterFactory.getInstance() + .create( + new io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model + .MetricExporter() + .withOtlp(new OtlpMetric()), + spiHelper, + closeables); + cleanup.addCloseable(exporter); + cleanup.addCloseables(closeables); + + 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(); + } + + @Test + void create_OtlpConfigured(@TempDir Path tempDir) + throws CertificateEncodingException, IOException { + spiHelper = spy(spiHelper); + List closeables = new ArrayList<>(); + OtlpHttpMetricExporter expectedExporter = + OtlpHttpMetricExporter.builder() + .setEndpoint("http://example:4318/v1/metrics") + .addHeader("key1", "value1") + .addHeader("key2", "value2") + .setTimeout(Duration.ofSeconds(15)) + .setCompression("gzip") + .build(); + cleanup.addCloseable(expectedExporter); + + // Write certificates to temp files + String certificatePath = + createTempFileWithContent( + tempDir, "certificate.cert", serverTls.certificate().getEncoded()); + String clientKeyPath = + createTempFileWithContent(tempDir, "clientKey.key", clientTls.privateKey().getEncoded()); + String clientCertificatePath = + createTempFileWithContent( + tempDir, "clientCertificate.cert", clientTls.certificate().getEncoded()); + + MetricExporter exporter = + MetricExporterFactory.getInstance() + .create( + new io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model + .MetricExporter() + .withOtlp( + new OtlpMetric() + .withProtocol("http/protobuf") + .withEndpoint("http://example:4318") + .withHeaders( + new Headers() + .withAdditionalProperty("key1", "value1") + .withAdditionalProperty("key2", "value2")) + .withCompression("gzip") + .withTimeout(15_000) + .withCertificate(certificatePath) + .withClientKey(clientKeyPath) + .withClientCertificate(clientCertificatePath) + .withTemporalityPreference("delta") + .withDefaultHistogramAggregation( + DefaultHistogramAggregation.BASE_2_EXPONENTIAL_BUCKET_HISTOGRAM)), + spiHelper, + closeables); + cleanup.addCloseable(exporter); + cleanup.addCloseables(closeables); + + 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")) + .isEqualTo("base2_exponential_bucket_histogram"); + } + + @Test + void create_Console() { + spiHelper = spy(spiHelper); + List closeables = new ArrayList<>(); + LoggingMetricExporter expectedExporter = LoggingMetricExporter.create(); + cleanup.addCloseable(expectedExporter); + + io.opentelemetry.sdk.metrics.export.MetricExporter exporter = + MetricExporterFactory.getInstance() + .create( + new io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model + .MetricExporter() + .withConsole(new Console()), + spiHelper, + closeables); + cleanup.addCloseable(exporter); + cleanup.addCloseables(closeables); + + assertThat(exporter.toString()).isEqualTo(expectedExporter.toString()); + } + + @Test + void create_PrometheusExporter() { + List closeables = new ArrayList<>(); + + assertThatThrownBy( + () -> + MetricExporterFactory.getInstance() + .create( + new io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model + .MetricExporter() + .withPrometheus(new Prometheus()), + spiHelper, + new ArrayList<>())) + .isInstanceOf(ConfigurationException.class) + .hasMessage("prometheus exporter not supported in this context"); + cleanup.addCloseables(closeables); + } + + @Test + void create_SpiExporter() { + List closeables = new ArrayList<>(); + + assertThatThrownBy( + () -> + MetricExporterFactory.getInstance() + .create( + new io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model + .MetricExporter() + .withAdditionalProperty("test", ImmutableMap.of("key1", "value1")), + spiHelper, + new ArrayList<>())) + .isInstanceOf(ConfigurationException.class) + .hasMessage("Unrecognized metric exporter(s): [test]"); + cleanup.addCloseables(closeables); + } +} diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactoryTest.java new file mode 100644 index 0000000000..ae1dfceedb --- /dev/null +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactoryTest.java @@ -0,0 +1,238 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +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 io.github.netmikey.logunit.api.LogCapturer; +import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; +import io.opentelemetry.exporter.prometheus.PrometheusHttpServer; +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.internal.ConfigurableMetricReaderProvider; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricExporter; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricReader; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OtlpMetric; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PeriodicMetricReader; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Prometheus; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PullMetricReader; +import java.io.Closeable; +import java.io.IOException; +import java.net.ServerSocket; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.ArgumentCaptor; + +class MetricReaderFactoryTest { + + @RegisterExtension CleanupExtension cleanup = new CleanupExtension(); + + @RegisterExtension + LogCapturer logCapturer = + LogCapturer.create().captureForLogger(ConfigurationFactory.class.getName()); + + private SpiHelper spiHelper = SpiHelper.create(MetricReaderFactoryTest.class.getClassLoader()); + + @Test + void create_Null() { + assertThat(MetricReaderFactory.getInstance().create(null, spiHelper, Collections.emptyList())) + .isNull(); + } + + @Test + void create_PeriodicNullExporter() { + assertThatThrownBy( + () -> + MetricReaderFactory.getInstance() + .create( + new MetricReader().withPeriodic(new PeriodicMetricReader()), + spiHelper, + Collections.emptyList())) + .isInstanceOf(ConfigurationException.class) + .hasMessage("exporter required for periodic reader"); + } + + @Test + void create_PeriodicDefaults() { + List closeables = new ArrayList<>(); + io.opentelemetry.sdk.metrics.export.PeriodicMetricReader expectedReader = + io.opentelemetry.sdk.metrics.export.PeriodicMetricReader.builder( + OtlpGrpcMetricExporter.getDefault()) + .build(); + cleanup.addCloseable(expectedReader); + + io.opentelemetry.sdk.metrics.export.MetricReader reader = + MetricReaderFactory.getInstance() + .create( + new MetricReader() + .withPeriodic( + new PeriodicMetricReader() + .withExporter(new MetricExporter().withOtlp(new OtlpMetric()))), + spiHelper, + closeables); + cleanup.addCloseable(reader); + cleanup.addCloseables(closeables); + + assertThat(reader.toString()).isEqualTo(expectedReader.toString()); + } + + @Test + void create_PeriodicConfigured() { + List closeables = new ArrayList<>(); + io.opentelemetry.sdk.metrics.export.MetricReader expectedReader = + io.opentelemetry.sdk.metrics.export.PeriodicMetricReader.builder( + OtlpGrpcMetricExporter.getDefault()) + .setInterval(Duration.ofMillis(1)) + .build(); + cleanup.addCloseable(expectedReader); + + io.opentelemetry.sdk.metrics.export.MetricReader reader = + MetricReaderFactory.getInstance() + .create( + new MetricReader() + .withPeriodic( + new PeriodicMetricReader() + .withExporter(new MetricExporter().withOtlp(new OtlpMetric())) + .withInterval(1)), + spiHelper, + closeables); + cleanup.addCloseable(reader); + cleanup.addCloseables(closeables); + + assertThat(reader.toString()).isEqualTo(expectedReader.toString()); + } + + @Test + void create_PullPrometheusDefault() throws IOException { + int port = randomAvailablePort(); + spiHelper = spy(spiHelper); + List closeables = new ArrayList<>(); + PrometheusHttpServer expectedReader = PrometheusHttpServer.builder().setPort(port).build(); + // Close the reader to avoid port conflict with the new instance created by MetricReaderFactory + expectedReader.close(); + + io.opentelemetry.sdk.metrics.export.MetricReader reader = + MetricReaderFactory.getInstance() + .create( + new MetricReader() + .withPull( + new PullMetricReader() + .withExporter( + new MetricExporter() + .withPrometheus(new Prometheus().withPort(port)))), + spiHelper, + closeables); + cleanup.addCloseable(reader); + cleanup.addCloseables(closeables); + + assertThat(reader.toString()).isEqualTo(expectedReader.toString()); + + ArgumentCaptor configCaptor = ArgumentCaptor.forClass(ConfigProperties.class); + verify(spiHelper) + .loadConfigurable( + eq(ConfigurableMetricReaderProvider.class), any(), any(), configCaptor.capture()); + ConfigProperties configProperties = configCaptor.getValue(); + assertThat(configProperties.getString("otel.exporter.prometheus.host")).isNull(); + assertThat(configProperties.getInt("otel.exporter.prometheus.port")).isEqualTo(port); + } + + @Test + void create_PullPrometheusConfigured() throws IOException { + int port = randomAvailablePort(); + + spiHelper = spy(spiHelper); + List closeables = new ArrayList<>(); + PrometheusHttpServer expectedReader = + PrometheusHttpServer.builder().setHost("localhost").setPort(port).build(); + // Close the reader to avoid port conflict with the new instance created by MetricReaderFactory + expectedReader.close(); + + io.opentelemetry.sdk.metrics.export.MetricReader reader = + MetricReaderFactory.getInstance() + .create( + new MetricReader() + .withPull( + new PullMetricReader() + .withExporter( + new MetricExporter() + .withPrometheus( + new Prometheus().withHost("localhost").withPort(port)))), + spiHelper, + closeables); + cleanup.addCloseable(reader); + cleanup.addCloseables(closeables); + + assertThat(reader.toString()).isEqualTo(expectedReader.toString()); + + ArgumentCaptor configCaptor = ArgumentCaptor.forClass(ConfigProperties.class); + verify(spiHelper) + .loadConfigurable( + eq(ConfigurableMetricReaderProvider.class), any(), any(), configCaptor.capture()); + ConfigProperties configProperties = configCaptor.getValue(); + assertThat(configProperties.getString("otel.exporter.prometheus.host")).isEqualTo("localhost"); + assertThat(configProperties.getInt("otel.exporter.prometheus.port")).isEqualTo(port); + } + + @Test + void create_InvalidPullReader() { + assertThatThrownBy( + () -> + MetricReaderFactory.getInstance() + .create( + new MetricReader().withPull(new PullMetricReader()), + spiHelper, + Collections.emptyList())) + .isInstanceOf(ConfigurationException.class) + .hasMessage("exporter required for pull reader"); + + assertThatThrownBy( + () -> + MetricReaderFactory.getInstance() + .create( + new MetricReader() + .withPull(new PullMetricReader().withExporter(new MetricExporter())), + spiHelper, + Collections.emptyList())) + .isInstanceOf(ConfigurationException.class) + .hasMessage("prometheus is the only currently supported pull reader"); + + assertThatThrownBy( + () -> + MetricReaderFactory.getInstance() + .create( + new MetricReader() + .withPull( + new PullMetricReader() + .withExporter(new MetricExporter().withOtlp(new OtlpMetric()))), + spiHelper, + Collections.emptyList())) + .isInstanceOf(ConfigurationException.class) + .hasMessage("prometheus is the only currently supported pull reader"); + } + + /** + * Find a random unused port. There's a small race if another process takes it before we + * initialize. Consider adding retries to this test if it flakes, presumably it never will on CI + * since there's no prometheus there blocking the well-known port. + */ + private static int randomAvailablePort() throws IOException { + try (ServerSocket socket2 = new ServerSocket(0)) { + return socket2.getLocalPort(); + } + } +} diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java index 5c3e5cd79a..9b31865193 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java @@ -14,6 +14,7 @@ import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter; +import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; import io.opentelemetry.extension.trace.propagation.B3Propagator; import io.opentelemetry.extension.trace.propagation.JaegerPropagator; @@ -30,16 +31,26 @@ import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRec import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordLimits; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordProcessor; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LoggerProvider; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MeterProvider; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricExporter; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricReader; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfiguration; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Otlp; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OtlpMetric; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PeriodicMetricReader; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Resource; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Sampler; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Selector; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SimpleLogRecordProcessor; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SpanExporter; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SpanProcessor; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Stream; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.TracerProvider; import io.opentelemetry.sdk.logs.LogLimits; import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.sdk.metrics.InstrumentSelector; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.View; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.SpanLimits; import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; @@ -194,6 +205,17 @@ class OpenTelemetryConfigurationFactoryTest { OtlpGrpcSpanExporter.getDefault()) .build()) .build()) + .setMeterProvider( + SdkMeterProvider.builder() + .setResource(expectedResource) + .registerMetricReader( + io.opentelemetry.sdk.metrics.export.PeriodicMetricReader.builder( + OtlpGrpcMetricExporter.getDefault()) + .build()) + .registerView( + InstrumentSelector.builder().setName("instrument-name").build(), + View.builder().setName("stream-name").build()) + .build()) .build(); cleanup.addCloseable(expectedSdk); @@ -243,7 +265,27 @@ class OpenTelemetryConfigurationFactoryTest { .withBatch( new BatchSpanProcessor() .withExporter( - new SpanExporter().withOtlp(new Otlp())))))), + new SpanExporter().withOtlp(new Otlp())))))) + .withMeterProvider( + new MeterProvider() + .withReaders( + Collections.singletonList( + new MetricReader() + .withPeriodic( + new PeriodicMetricReader() + .withExporter( + new MetricExporter() + .withOtlp(new OtlpMetric()))))) + .withViews( + Collections.singletonList( + new io.opentelemetry.sdk.extension.incubator.fileconfig.internal + .model.View() + .withSelector( + new Selector().withInstrumentName("instrument-name")) + .withStream( + new Stream() + .withName("stream-name") + .withAttributeKeys(null))))), spiHelper, closeables); cleanup.addCloseable(sdk); 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 131f77a37a..2d12c4e7b8 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 @@ -15,6 +15,7 @@ import static org.mockito.Mockito.verify; import com.google.common.collect.ImmutableMap; import com.linecorp.armeria.testing.junit5.server.SelfSignedCertificateExtension; +import io.opentelemetry.exporter.logging.LoggingSpanExporter; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; import io.opentelemetry.internal.testing.CleanupExtension; @@ -22,6 +23,7 @@ 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.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; import io.opentelemetry.sdk.trace.export.SpanExporter; @@ -163,6 +165,27 @@ class SpanExporterFactoryTest { .isEqualTo(clientCertificatePath); } + @Test + void create_Console() { + spiHelper = spy(spiHelper); + List closeables = new ArrayList<>(); + LoggingSpanExporter expectedExporter = LoggingSpanExporter.create(); + cleanup.addCloseable(expectedExporter); + + SpanExporter exporter = + SpanExporterFactory.getInstance() + .create( + new io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model + .SpanExporter() + .withConsole(new Console()), + spiHelper, + closeables); + cleanup.addCloseable(exporter); + cleanup.addCloseables(closeables); + + assertThat(exporter.toString()).isEqualTo(expectedExporter.toString()); + } + @Test void create_SpiExporter() { List closeables = new ArrayList<>(); diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ViewFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ViewFactoryTest.java new file mode 100644 index 0000000000..e04d5460a2 --- /dev/null +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ViewFactoryTest.java @@ -0,0 +1,78 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; + +import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Aggregation; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExplicitBucketHistogram; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Stream; +import io.opentelemetry.sdk.metrics.View; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import org.junit.jupiter.api.Test; + +class ViewFactoryTest { + + @Test + void create_Null() { + assertThatThrownBy( + () -> + ViewFactory.getInstance() + .create(null, mock(SpiHelper.class), Collections.emptyList())) + .isInstanceOf(ConfigurationException.class) + .hasMessage("stream must not be null"); + } + + @Test + void create_Defaults() { + View expectedView = View.builder().build(); + + View view = + ViewFactory.getInstance() + .create( + new Stream().withAttributeKeys(null), + mock(SpiHelper.class), + Collections.emptyList()); + + assertThat(view.toString()).isEqualTo(expectedView.toString()); + } + + @Test + void create() { + View expectedView = + View.builder() + .setName("name") + .setDescription("description") + .setAttributeFilter(new HashSet<>(Arrays.asList("foo", "bar"))) + .setAggregation( + io.opentelemetry.sdk.metrics.Aggregation.explicitBucketHistogram( + Arrays.asList(1.0, 2.0))) + .build(); + + View view = + ViewFactory.getInstance() + .create( + new Stream() + .withName("name") + .withDescription("description") + .withAttributeKeys(Arrays.asList("foo", "bar")) + .withAggregation( + new Aggregation() + .withExplicitBucketHistogram( + new ExplicitBucketHistogram() + .withBoundaries(Arrays.asList(1.0, 2.0)))), + mock(SpiHelper.class), + Collections.emptyList()); + + assertThat(view.toString()).isEqualTo(expectedView.toString()); + } +}