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
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();
}
@Override
public String toString() {
return "LoggingMetricExporter{}";
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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

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,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<String, String> 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<String, String> 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<LogRecordExporter> logRecordExporterSpiManager(
ConfigProperties config, SpiHelper spiHelper) {
return spiHelper.loadConfigurable(

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

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.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<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
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<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(
ConfigProperties config, SpiHelper spiHelper) {
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 =
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);

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.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);

View File

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