Meter provider configuration factory (#5773)

This commit is contained in:
jack-berg 2023-08-28 14:22:03 -05:00 committed by GitHub
parent 7229c45513
commit 01503efe97
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1552 additions and 93 deletions

View File

@ -1,2 +1,10 @@
Comparing source compatibility of against Comparing source compatibility of against
No changes. *** 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()

View File

@ -103,4 +103,9 @@ public final class LoggingMetricExporter implements MetricExporter {
} }
return flush(); return flush();
} }
@Override
public String toString() {
return "LoggingMetricExporter{}";
}
} }

View File

@ -93,4 +93,9 @@ public final class LoggingSpanExporter implements SpanExporter {
} }
return flush(); return flush();
} }
@Override
public String toString() {
return "LoggingSpanExporter{}";
}
} }

View File

@ -95,4 +95,9 @@ public class SystemOutLogRecordExporter implements LogRecordExporter {
} }
return CompletableResultCode.ofSuccess(); return CompletableResultCode.ofSuccess();
} }
@Override
public String toString() {
return "SystemOutLogRecordExporter{}";
}
} }

View File

@ -114,4 +114,9 @@ class LoggingMetricExporterTest {
assertThat(exporter.shutdown().isSuccess()).isTrue(); assertThat(exporter.shutdown().isSuccess()).isTrue();
logs.assertContains("Calling shutdown() multiple times."); logs.assertContains("Calling shutdown() multiple times.");
} }
@Test
void stringRepresentation() {
assertThat(LoggingMetricExporter.create().toString()).isEqualTo("LoggingMetricExporter{}");
}
} }

View File

@ -139,4 +139,9 @@ class LoggingSpanExporterTest {
assertThat(exporter.shutdown().isSuccess()).isTrue(); assertThat(exporter.shutdown().isSuccess()).isTrue();
logs.assertContains("Calling shutdown() multiple times."); logs.assertContains("Calling shutdown() multiple times.");
} }
@Test
void stringRepresentation() {
assertThat(LoggingSpanExporter.create().toString()).isEqualTo("LoggingSpanExporter{}");
}
} }

View File

@ -61,6 +61,12 @@ class SystemOutLogRecordExporterTest {
assertThat(exporter.shutdown().isSuccess()).isTrue(); assertThat(exporter.shutdown().isSuccess()).isTrue();
} }
@Test
void stringRepresentation() {
assertThat(SystemOutLogRecordExporter.create().toString())
.isEqualTo("SystemOutLogRecordExporter{}");
}
private static LogRecordData sampleLog(long timestamp) { private static LogRecordData sampleLog(long timestamp) {
return TestLogRecordData.builder() return TestLogRecordData.builder()
.setResource(Resource.empty()) .setResource(Resource.empty())

View File

@ -33,6 +33,8 @@ dependencies {
testImplementation(project(":sdk:testing")) testImplementation(project(":sdk:testing"))
testImplementation(project(":sdk-extensions:autoconfigure")) testImplementation(project(":sdk-extensions:autoconfigure"))
testImplementation(project(":exporters:otlp:all")) testImplementation(project(":exporters:otlp:all"))
testImplementation(project(":exporters:prometheus"))
testImplementation(project(":exporters:logging"))
testImplementation(project(":sdk-extensions:jaeger-remote-sampler")) testImplementation(project(":sdk-extensions:jaeger-remote-sampler"))
testImplementation(project(":extensions:trace-propagators")) 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 // 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

View File

@ -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<Aggregation, io.opentelemetry.sdk.metrics.Aggregation> {
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<Closeable> 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<Double> 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();
}
}

View File

@ -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<Selector, InstrumentSelector> {
private static final InstrumentSelectorFactory INSTANCE = new InstrumentSelectorFactory();
private InstrumentSelectorFactory() {}
static InstrumentSelectorFactory getInstance() {
return INSTANCE;
}
@Override
public InstrumentSelector create(
@Nullable Selector model, SpiHelper spiHelper, List<Closeable> 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);
}
}
}

View File

@ -45,9 +45,22 @@ final class LogRecordExporterFactory
return LogRecordExporter.composite(); return LogRecordExporter.composite();
} }
if (model.getOtlp() != null) { Otlp otlpModel = model.getOtlp();
Otlp otlp = model.getOtlp(); if (otlpModel != null) {
return FileConfigUtil.addAndReturn(closeables, createOtlpExporter(otlpModel, spiHelper));
}
// 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(",", "[", "]")));
}
return LogRecordExporter.composite();
}
private static LogRecordExporter createOtlpExporter(Otlp otlp, SpiHelper spiHelper) {
// Translate from file configuration scheme to environment variable scheme. This is ultimately // Translate from file configuration scheme to environment variable scheme. This is ultimately
// interpreted by Otlp*ExporterProviders, but we want to avoid the dependency on // interpreted by Otlp*ExporterProviders, but we want to avoid the dependency on
// opentelemetry-exporter-otlp // opentelemetry-exporter-otlp
@ -87,21 +100,9 @@ final class LogRecordExporterFactory
// TODO(jack-berg): add method for creating from map // TODO(jack-berg): add method for creating from map
ConfigProperties configProperties = DefaultConfigProperties.createForTest(properties); ConfigProperties configProperties = DefaultConfigProperties.createForTest(properties);
return FileConfigUtil.addAndReturn( return FileConfigUtil.assertNotNull(
closeables,
FileConfigUtil.assertNotNull(
logRecordExporterSpiManager(configProperties, spiHelper).getByName("otlp"), logRecordExporterSpiManager(configProperties, spiHelper).getByName("otlp"),
"otlp exporter")); "otlp exporter");
}
// 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(",", "[", "]")));
}
return LogRecordExporter.composite();
} }
private static NamedSpiManager<LogRecordExporter> logRecordExporterSpiManager( private static NamedSpiManager<LogRecordExporter> logRecordExporterSpiManager(

View File

@ -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<MeterProvider, SdkMeterProviderBuilder> {
private static final MeterProviderFactory INSTANCE = new MeterProviderFactory();
private MeterProviderFactory() {}
static MeterProviderFactory getInstance() {
return INSTANCE;
}
@Override
public SdkMeterProviderBuilder create(
@Nullable MeterProvider model, SpiHelper spiHelper, List<Closeable> closeables) {
if (model == null) {
return SdkMeterProvider.builder();
}
SdkMeterProviderBuilder builder = SdkMeterProvider.builder();
List<MetricReader> 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<View> 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;
}
}

View File

@ -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<Closeable> 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<String, String> 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<MetricExporter> metricExporterSpiManager(
ConfigProperties config, SpiHelper spiHelper) {
return spiHelper.loadConfigurable(
ConfigurableMetricExporterProvider.class,
ConfigurableMetricExporterProvider::getName,
ConfigurableMetricExporterProvider::createExporter,
config);
}
}

View File

@ -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<Closeable> 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<String, String> 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<io.opentelemetry.sdk.metrics.export.MetricReader>
metricReaderSpiManager(ConfigProperties config, SpiHelper spiHelper) {
return spiHelper.loadConfigurable(
ConfigurableMetricReaderProvider.class,
ConfigurableMetricReaderProvider::getName,
ConfigurableMetricReaderProvider::createMetricReader,
config);
}
}

View File

@ -71,7 +71,16 @@ final class OpenTelemetryConfigurationFactory
.build())); .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 // TODO(jack-berg): add support for general attribute limits
return FileConfigUtil.addAndReturn(closeables, builder.build()); return FileConfigUtil.addAndReturn(closeables, builder.build());

View File

@ -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.extension.incubator.fileconfig.internal.model.Otlp;
import io.opentelemetry.sdk.trace.export.SpanExporter; import io.opentelemetry.sdk.trace.export.SpanExporter;
import java.io.Closeable; import java.io.Closeable;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -44,53 +45,19 @@ final class SpanExporterFactory
return SpanExporter.composite(); return SpanExporter.composite();
} }
if (model.getOtlp() != null) { Otlp otlpModel = model.getOtlp();
Otlp otlp = model.getOtlp(); if (otlpModel != null) {
return FileConfigUtil.addAndReturn(closeables, createOtlpExporter(otlpModel, 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<String, String> 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 if (model.getConsole() != null) {
ConfigProperties configProperties = DefaultConfigProperties.createForTest(properties);
return FileConfigUtil.addAndReturn( return FileConfigUtil.addAndReturn(
closeables, closeables,
FileConfigUtil.assertNotNull( FileConfigUtil.assertNotNull(
spanExporterSpiManager(configProperties, spiHelper).getByName("otlp"), spanExporterSpiManager(
"otlp exporter")); DefaultConfigProperties.createForTest(Collections.emptyMap()), spiHelper)
.getByName("logging"),
"logging exporter"));
} }
// TODO(jack-berg): add support for generic SPI exporters // TODO(jack-berg): add support for generic SPI exporters
@ -103,6 +70,50 @@ final class SpanExporterFactory
return SpanExporter.composite(); 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<String, String> 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<SpanExporter> spanExporterSpiManager( private static NamedSpiManager<SpanExporter> spanExporterSpiManager(
ConfigProperties config, SpiHelper spiHelper) { ConfigProperties config, SpiHelper spiHelper) {
return spiHelper.loadConfigurable( return spiHelper.loadConfigurable(

View File

@ -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<Stream, View> {
private static final ViewFactory INSTANCE = new ViewFactory();
private ViewFactory() {}
static ViewFactory getInstance() {
return INSTANCE;
}
@Override
public View create(@Nullable Stream model, SpiHelper spiHelper, List<Closeable> 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();
}
}

View File

@ -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<Arguments> 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))));
}
}

View File

@ -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());
}
}

View File

@ -56,6 +56,16 @@ class LogRecordExporterFactoryTest {
private SpiHelper spiHelper = private SpiHelper spiHelper =
SpiHelper.create(LogRecordExporterFactoryTest.class.getClassLoader()); 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 @Test
void create_OtlpDefaults() { void create_OtlpDefaults() {
spiHelper = spy(spiHelper); spiHelper = spy(spiHelper);

View File

@ -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<Closeable> 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<Closeable> 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<Closeable> 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());
}
}

View File

@ -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<Closeable> 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<ConfigProperties> 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<Closeable> 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<ConfigProperties> 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<Closeable> 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<Closeable> 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<Closeable> 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);
}
}

View File

@ -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<Closeable> 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<Closeable> 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<Closeable> 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<ConfigProperties> 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<Closeable> 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<ConfigProperties> 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();
}
}
}

View File

@ -14,6 +14,7 @@ import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter; import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter;
import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.extension.trace.propagation.B3Propagator; import io.opentelemetry.extension.trace.propagation.B3Propagator;
import io.opentelemetry.extension.trace.propagation.JaegerPropagator; 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.LogRecordLimits;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordProcessor; 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.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.OpenTelemetryConfiguration;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Otlp; 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.Resource;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Sampler; 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.SimpleLogRecordProcessor;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SpanExporter; 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.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.extension.incubator.fileconfig.internal.model.TracerProvider;
import io.opentelemetry.sdk.logs.LogLimits; import io.opentelemetry.sdk.logs.LogLimits;
import io.opentelemetry.sdk.logs.SdkLoggerProvider; 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.SdkTracerProvider;
import io.opentelemetry.sdk.trace.SpanLimits; import io.opentelemetry.sdk.trace.SpanLimits;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
@ -194,6 +205,17 @@ class OpenTelemetryConfigurationFactoryTest {
OtlpGrpcSpanExporter.getDefault()) OtlpGrpcSpanExporter.getDefault())
.build()) .build())
.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(); .build();
cleanup.addCloseable(expectedSdk); cleanup.addCloseable(expectedSdk);
@ -243,7 +265,27 @@ class OpenTelemetryConfigurationFactoryTest {
.withBatch( .withBatch(
new BatchSpanProcessor() new BatchSpanProcessor()
.withExporter( .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, spiHelper,
closeables); closeables);
cleanup.addCloseable(sdk); cleanup.addCloseable(sdk);

View File

@ -15,6 +15,7 @@ import static org.mockito.Mockito.verify;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.linecorp.armeria.testing.junit5.server.SelfSignedCertificateExtension; 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.http.trace.OtlpHttpSpanExporter;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.internal.testing.CleanupExtension; 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.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider; 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.Headers;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Otlp; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Otlp;
import io.opentelemetry.sdk.trace.export.SpanExporter; import io.opentelemetry.sdk.trace.export.SpanExporter;
@ -163,6 +165,27 @@ class SpanExporterFactoryTest {
.isEqualTo(clientCertificatePath); .isEqualTo(clientCertificatePath);
} }
@Test
void create_Console() {
spiHelper = spy(spiHelper);
List<Closeable> 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 @Test
void create_SpiExporter() { void create_SpiExporter() {
List<Closeable> closeables = new ArrayList<>(); List<Closeable> closeables = new ArrayList<>();

View File

@ -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());
}
}