diff --git a/sdk-extensions/incubator/build.gradle.kts b/sdk-extensions/incubator/build.gradle.kts
index 562cfaaa989..a7977b5e82a 100644
--- a/sdk-extensions/incubator/build.gradle.kts
+++ b/sdk-extensions/incubator/build.gradle.kts
@@ -27,9 +27,12 @@ dependencies {
// io.opentelemetry.sdk.extension.incubator.fileconfig
implementation("com.fasterxml.jackson.core:jackson-databind")
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml")
+ implementation(project(":sdk-extensions:autoconfigure"))
testImplementation(project(":sdk:testing"))
testImplementation(project(":sdk-extensions:autoconfigure"))
+ testImplementation(project(":exporters:otlp:all"))
+ testImplementation("com.linecorp.armeria:armeria-junit5")
testImplementation("com.google.guava:guava-testlib")
}
@@ -74,6 +77,10 @@ jsonSchema2Pojo {
// Clear old source files to avoid contaminated source dir when updating
removeOldOutput = true
+ // Include @Nullable annotation. Note: jsonSchmea2Pojo will not add @Nullable annotations on getters
+ // so we perform some steps in jsonSchema2PojoPostProcessing to add these.
+ includeJsr305Annotations = true
+
// Prefer builders to setters
includeSetters = false
generateBuilders = true
@@ -96,10 +103,14 @@ val jsonSchema2PojoPostProcessing by tasks.registering(Copy::class) {
into("$buildDir/generated/sources/js2p-tmp")
filter {
it
- // Replace java 9+ @Generated annotation with java 8 version
- .replace("import javax.annotation.processing.Generated", "import javax.annotation.Generated")
+ // Remove @Nullable annotation so it can be deterministically added later
+ .replace("import javax.annotation.Nullable;\n", "")
+ // Replace java 9+ @Generated annotation with java 8 version, add @Nullable annotation
+ .replace("import javax.annotation.processing.Generated;", "import javax.annotation.Nullable;\nimport javax.annotation.Generated;")
// Add @SuppressWarnings("rawtypes") annotation to address raw types used in jsonschema2pojo builders
.replace("@Generated(\"jsonschema2pojo\")", "@Generated(\"jsonschema2pojo\")\n@SuppressWarnings(\"rawtypes\")")
+ // Add @Nullable annotations to all getters
+ .replace("( *)public ([a-zA-Z]*) get([a-zA-Z]*)".toRegex(), "$1@Nullable\n$1public $2 get$3")
}
}
val overwriteJs2p by tasks.registering(Copy::class) {
diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ConfigurationFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ConfigurationFactory.java
new file mode 100644
index 00000000000..1e0dddf3074
--- /dev/null
+++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ConfigurationFactory.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.sdk.extension.incubator.fileconfig;
+
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper;
+import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
+import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfiguration;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Logger;
+
+/**
+ * Parses YAML configuration files conforming to the schema in open-telemetry/opentelemetry-configuration
+ * to a {@link OpenTelemetryConfiguration} in-memory representation. Interprets the in-memory
+ * representation to produce an {@link OpenTelemetrySdk}.
+ *
+ * @see #parseAndInterpret(InputStream)
+ */
+public final class ConfigurationFactory {
+
+ private static final Logger logger = Logger.getLogger(ConfigurationFactory.class.getName());
+
+ private ConfigurationFactory() {}
+
+ /**
+ * Parse the {@code inputStream} YAML to {@link OpenTelemetryConfiguration} and interpret the
+ * model to create {@link OpenTelemetrySdk} instance corresponding to the configuration.
+ *
+ * @param inputStream the configuration YAML
+ * @return the {@link OpenTelemetrySdk}
+ */
+ public static OpenTelemetrySdk parseAndInterpret(InputStream inputStream) {
+ OpenTelemetryConfiguration model;
+ try {
+ model = ConfigurationReader.parse(inputStream);
+ } catch (RuntimeException e) {
+ throw new ConfigurationException("Unable to parse inputStream", e);
+ }
+
+ List closeables = new ArrayList<>();
+ try {
+ return OpenTelemetryConfigurationFactory.getInstance()
+ .create(model, SpiHelper.create(ConfigurationFactory.class.getClassLoader()), closeables);
+ } catch (RuntimeException e) {
+ logger.info(
+ "Error encountered interpreting configuration. Closing partially configured components.");
+ for (Closeable closeable : closeables) {
+ try {
+ logger.fine("Closing " + closeable.getClass().getName());
+ closeable.close();
+ } catch (IOException ex) {
+ logger.warning(
+ "Error closing " + closeable.getClass().getName() + ": " + ex.getMessage());
+ }
+ }
+ if (e instanceof ConfigurationException) {
+ throw e;
+ }
+ throw new ConfigurationException("Unexpected configuration error", e);
+ }
+ }
+}
diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ConfigurationReader.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ConfigurationReader.java
index cb211a78571..04aaaa894ef 100644
--- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ConfigurationReader.java
+++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ConfigurationReader.java
@@ -11,7 +11,7 @@
import org.snakeyaml.engine.v2.api.Load;
import org.snakeyaml.engine.v2.api.LoadSettings;
-class ConfigurationReader {
+final class ConfigurationReader {
private static final ObjectMapper MAPPER = new ObjectMapper();
diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/Factory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/Factory.java
new file mode 100644
index 00000000000..609fcd4f902
--- /dev/null
+++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/Factory.java
@@ -0,0 +1,24 @@
+/*
+ * 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 java.io.Closeable;
+import java.util.List;
+import javax.annotation.Nullable;
+
+interface Factory {
+
+ /**
+ * Interpret the model and create {@link ResultT} with corresponding configuration.
+ *
+ * @param model the configuration model
+ * @param spiHelper the service loader helper
+ * @param closeables mutable list of closeables created
+ * @return the {@link ResultT}
+ */
+ ResultT create(@Nullable ModelT model, SpiHelper spiHelper, List closeables);
+}
diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigUtil.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigUtil.java
new file mode 100644
index 00000000000..3aacd194073
--- /dev/null
+++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigUtil.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.sdk.extension.incubator.fileconfig;
+
+import java.io.Closeable;
+import java.util.List;
+import javax.annotation.Nullable;
+
+final class FileConfigUtil {
+
+ private FileConfigUtil() {}
+
+ /** Add the {@code closeable} to the {@code closeables} and return it. */
+ static T addAndReturn(List closeables, T closeable) {
+ closeables.add(closeable);
+ return closeable;
+ }
+
+ static T assertNotNull(@Nullable T object, String description) {
+ if (object == null) {
+ throw new NullPointerException(description + " is null");
+ }
+ return object;
+ }
+}
diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogLimitsFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogLimitsFactory.java
new file mode 100644
index 00000000000..ca803fc593b
--- /dev/null
+++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogLimitsFactory.java
@@ -0,0 +1,42 @@
+/*
+ * 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.LogRecordLimits;
+import io.opentelemetry.sdk.logs.LogLimits;
+import io.opentelemetry.sdk.logs.LogLimitsBuilder;
+import java.io.Closeable;
+import java.util.List;
+import javax.annotation.Nullable;
+
+final class LogLimitsFactory implements Factory {
+
+ private static final LogLimitsFactory INSTANCE = new LogLimitsFactory();
+
+ private LogLimitsFactory() {}
+
+ static LogLimitsFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public LogLimits create(
+ @Nullable LogRecordLimits model, SpiHelper spiHelper, List closeables) {
+ if (model == null) {
+ return LogLimits.getDefault();
+ }
+
+ LogLimitsBuilder builder = LogLimits.builder();
+ if (model.getAttributeCountLimit() != null) {
+ builder.setMaxNumberOfAttributes(model.getAttributeCountLimit());
+ }
+ if (model.getAttributeValueLengthLimit() != null) {
+ builder.setMaxAttributeValueLength(model.getAttributeValueLengthLimit());
+ }
+ return builder.build();
+ }
+}
diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordExporterFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordExporterFactory.java
new file mode 100644
index 00000000000..23e9851989f
--- /dev/null
+++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordExporterFactory.java
@@ -0,0 +1,112 @@
+/*
+ * 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.logs.ConfigurableLogRecordExporterProvider;
+import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Otlp;
+import io.opentelemetry.sdk.logs.export.LogRecordExporter;
+import java.io.Closeable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
+
+final class LogRecordExporterFactory
+ implements Factory<
+ io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordExporter,
+ LogRecordExporter> {
+
+ private static final LogRecordExporterFactory INSTANCE = new LogRecordExporterFactory();
+
+ private LogRecordExporterFactory() {}
+
+ static LogRecordExporterFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public LogRecordExporter create(
+ @Nullable
+ io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordExporter
+ model,
+ SpiHelper spiHelper,
+ List closeables) {
+ if (model == null) {
+ return LogRecordExporter.composite();
+ }
+
+ if (model.getOtlp() != null) {
+ Otlp otlp = model.getOtlp();
+
+ // Translate from file configuration scheme to environment variable scheme. This is ultimately
+ // interpreted by Otlp*ExporterProviders, but we want to avoid the dependency on
+ // opentelemetry-exporter-otlp
+ Map properties = new HashMap<>();
+ if (otlp.getProtocol() != null) {
+ properties.put("otel.exporter.otlp.logs.protocol", otlp.getProtocol());
+ }
+ if (otlp.getEndpoint() != null) {
+ properties.put("otel.exporter.otlp.logs.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"));
+ }
+
+ // 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 logRecordExporterSpiManager(
+ ConfigProperties config, SpiHelper spiHelper) {
+ return spiHelper.loadConfigurable(
+ ConfigurableLogRecordExporterProvider.class,
+ ConfigurableLogRecordExporterProvider::getName,
+ ConfigurableLogRecordExporterProvider::createExporter,
+ config);
+ }
+}
diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordProcessorFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordProcessorFactory.java
new file mode 100644
index 00000000000..5971d8537a9
--- /dev/null
+++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordProcessorFactory.java
@@ -0,0 +1,95 @@
+/*
+ * 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.SpiHelper;
+import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
+import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordExporter;
+import io.opentelemetry.sdk.logs.LogRecordProcessor;
+import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor;
+import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessorBuilder;
+import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor;
+import java.io.Closeable;
+import java.time.Duration;
+import java.util.List;
+import javax.annotation.Nullable;
+
+final class LogRecordProcessorFactory
+ implements Factory<
+ io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordProcessor,
+ LogRecordProcessor> {
+
+ private static final LogRecordProcessorFactory INSTANCE = new LogRecordProcessorFactory();
+
+ private LogRecordProcessorFactory() {}
+
+ static LogRecordProcessorFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public LogRecordProcessor create(
+ @Nullable
+ io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordProcessor
+ model,
+ SpiHelper spiHelper,
+ List closeables) {
+ if (model == null) {
+ return LogRecordProcessor.composite();
+ }
+
+ io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchLogRecordProcessor
+ batchModel = model.getBatch();
+ if (batchModel != null) {
+ LogRecordExporter exporterModel = batchModel.getExporter();
+ if (exporterModel == null) {
+ return LogRecordProcessor.composite();
+ }
+
+ BatchLogRecordProcessorBuilder builder =
+ BatchLogRecordProcessor.builder(
+ LogRecordExporterFactory.getInstance().create(exporterModel, spiHelper, closeables));
+ if (batchModel.getExportTimeout() != null) {
+ builder.setExporterTimeout(Duration.ofMillis(batchModel.getExportTimeout()));
+ }
+ if (batchModel.getMaxExportBatchSize() != null) {
+ builder.setMaxExportBatchSize(batchModel.getMaxExportBatchSize());
+ }
+ if (batchModel.getMaxQueueSize() != null) {
+ builder.setMaxQueueSize(batchModel.getMaxQueueSize());
+ }
+ if (batchModel.getScheduleDelay() != null) {
+ builder.setScheduleDelay(Duration.ofMillis(batchModel.getScheduleDelay()));
+ }
+ return FileConfigUtil.addAndReturn(closeables, builder.build());
+ }
+
+ io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SimpleLogRecordProcessor
+ simpleModel = model.getSimple();
+ if (simpleModel != null) {
+ LogRecordExporter exporterModel = simpleModel.getExporter();
+ if (exporterModel == null) {
+ return LogRecordProcessor.composite();
+ }
+
+ return FileConfigUtil.addAndReturn(
+ closeables,
+ SimpleLogRecordProcessor.create(
+ LogRecordExporterFactory.getInstance().create(exporterModel, spiHelper, closeables)));
+ }
+
+ // TODO: add support for generic log record processors
+ if (!model.getAdditionalProperties().isEmpty()) {
+ throw new ConfigurationException(
+ "Unrecognized log record processor(s): "
+ + model.getAdditionalProperties().keySet().stream().collect(joining(",", "[", "]")));
+ }
+
+ return LogRecordProcessor.composite();
+ }
+}
diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LoggerProviderFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LoggerProviderFactory.java
new file mode 100644
index 00000000000..268cba98379
--- /dev/null
+++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LoggerProviderFactory.java
@@ -0,0 +1,52 @@
+/*
+ * 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.LogRecordProcessor;
+import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LoggerProvider;
+import io.opentelemetry.sdk.logs.LogLimits;
+import io.opentelemetry.sdk.logs.SdkLoggerProvider;
+import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder;
+import java.io.Closeable;
+import java.util.List;
+import javax.annotation.Nullable;
+
+final class LoggerProviderFactory implements Factory {
+
+ private static final LoggerProviderFactory INSTANCE = new LoggerProviderFactory();
+
+ private LoggerProviderFactory() {}
+
+ static LoggerProviderFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public SdkLoggerProviderBuilder create(
+ @Nullable LoggerProvider model, SpiHelper spiHelper, List closeables) {
+ if (model == null) {
+ return SdkLoggerProvider.builder();
+ }
+
+ SdkLoggerProviderBuilder builder = SdkLoggerProvider.builder();
+
+ LogLimits logLimits =
+ LogLimitsFactory.getInstance().create(model.getLimits(), spiHelper, closeables);
+ builder.setLogLimits(() -> logLimits);
+
+ List processors = model.getProcessors();
+ if (processors != null) {
+ processors.forEach(
+ processor ->
+ builder.addLogRecordProcessor(
+ LogRecordProcessorFactory.getInstance()
+ .create(processor, spiHelper, closeables)));
+ }
+
+ return builder;
+ }
+}
diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java
new file mode 100644
index 00000000000..d121662d5ce
--- /dev/null
+++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.sdk.extension.incubator.fileconfig;
+
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import io.opentelemetry.sdk.OpenTelemetrySdkBuilder;
+import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper;
+import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
+import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfiguration;
+import java.io.Closeable;
+import java.util.List;
+import javax.annotation.Nullable;
+
+final class OpenTelemetryConfigurationFactory
+ implements Factory {
+
+ private static final OpenTelemetryConfigurationFactory INSTANCE =
+ new OpenTelemetryConfigurationFactory();
+
+ private OpenTelemetryConfigurationFactory() {}
+
+ static OpenTelemetryConfigurationFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public OpenTelemetrySdk create(
+ @Nullable OpenTelemetryConfiguration model, SpiHelper spiHelper, List closeables) {
+ if (model == null) {
+ return FileConfigUtil.addAndReturn(closeables, OpenTelemetrySdk.builder().build());
+ }
+
+ if (!"0.1".equals(model.getFileFormat())) {
+ throw new ConfigurationException("Unsupported file format. Supported formats include: 0.1");
+ }
+
+ OpenTelemetrySdkBuilder builder = OpenTelemetrySdk.builder();
+
+ if (model.getLoggerProvider() != null) {
+ builder.setLoggerProvider(
+ FileConfigUtil.addAndReturn(
+ closeables,
+ LoggerProviderFactory.getInstance()
+ .create(model.getLoggerProvider(), spiHelper, closeables)
+ .build()));
+ }
+
+ // TODO(jack-berg): add support for tracer provider
+ // TODO(jack-berg): add support for meter provider
+ // TODO(jack-berg): add support for propagators
+ // TODO(jack-berg): add support for resource
+ // TODO(jack-berg): add support for general attribute limits
+
+ return FileConfigUtil.addAndReturn(closeables, builder.build());
+ }
+}
diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ConfigurationFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ConfigurationFactoryTest.java
new file mode 100644
index 00000000000..2054e967987
--- /dev/null
+++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ConfigurationFactoryTest.java
@@ -0,0 +1,138 @@
+/*
+ * 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.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import com.linecorp.armeria.testing.junit5.server.SelfSignedCertificateExtension;
+import io.github.netmikey.logunit.api.LogCapturer;
+import io.opentelemetry.internal.testing.CleanupExtension;
+import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.cert.CertificateEncodingException;
+import java.util.Objects;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.api.io.TempDir;
+import org.slf4j.event.Level;
+
+class ConfigurationFactoryTest {
+
+ @RegisterExtension
+ static final SelfSignedCertificateExtension serverTls = new SelfSignedCertificateExtension();
+
+ @RegisterExtension
+ static final SelfSignedCertificateExtension clientTls = new SelfSignedCertificateExtension();
+
+ @RegisterExtension CleanupExtension cleanup = new CleanupExtension();
+
+ @RegisterExtension
+ LogCapturer logCapturer =
+ LogCapturer.create().captureForLogger(ConfigurationFactory.class.getName(), Level.TRACE);
+
+ @Test
+ void parseAndInterpret_BadInputStream() {
+ assertThatThrownBy(
+ () ->
+ ConfigurationFactory.parseAndInterpret(
+ new ByteArrayInputStream("foo".getBytes(StandardCharsets.UTF_8))))
+ .isInstanceOf(ConfigurationException.class)
+ .hasMessage("Unable to parse inputStream");
+ }
+
+ /**
+ * Verify each example in open-telemetry/opentelemetry-configuration/examples
+ * can pass {@link ConfigurationFactory#parseAndInterpret(InputStream)}.
+ */
+ @Test
+ void parseAndInterpret_Examples(@TempDir Path tempDir)
+ throws IOException, CertificateEncodingException {
+ // 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());
+
+ File examplesDir = new File(System.getenv("CONFIG_EXAMPLE_DIR"));
+ assertThat(examplesDir.isDirectory()).isTrue();
+
+ for (File example : Objects.requireNonNull(examplesDir.listFiles())) {
+ // Skip anchors.yaml because support for merge (i.e. "<<: *anchor") was explicitly removed in
+ // snakeyaml-engine:
+ // https://bitbucket.org/snakeyaml/snakeyaml-engine/issues/18/merge-tag-support
+ // As discussed in this issue merge is supported in snakeyaml:
+ // https://bitbucket.org/snakeyaml/snakeyaml-engine/issues/14/read-in-yaml-with-merge-then-dump-strips
+ // TODO(jack-berg): decide if we should try to support anchors, or remove anchors example from
+ // opentelemetry-configuration
+ if (example.getName().equals("anchors.yaml")) {
+ continue;
+ }
+
+ // Rewrite references to cert files in examples
+ String exampleContent =
+ new String(Files.readAllBytes(example.toPath()), StandardCharsets.UTF_8);
+ String rewrittenExampleContent =
+ exampleContent
+ .replaceAll(
+ "certificate: .*\n", "certificate: " + certificatePath + System.lineSeparator())
+ .replaceAll(
+ "client_key: .*\n", "client_key: " + clientKeyPath + System.lineSeparator())
+ .replaceAll(
+ "client_certificate: .*\n",
+ "client_certificate: " + clientCertificatePath + System.lineSeparator());
+ InputStream is =
+ new ByteArrayInputStream(rewrittenExampleContent.getBytes(StandardCharsets.UTF_8));
+
+ // Verify that file can be parsed and interpreted without error
+ assertThatCode(() -> cleanup.addCloseable(ConfigurationFactory.parseAndInterpret(is)))
+ .as("Example file: " + example.getName())
+ .doesNotThrowAnyException();
+ }
+ }
+
+ @Test
+ void parseAndInterpret_Exception_CleansUpPartials() {
+ // Trigger an exception after some components have been configured by adding a valid batch
+ // exporter with OTLP exporter, following by invalid batch exporter which references invalid
+ // exporter "foo".
+ String yaml =
+ "file_format: \"0.1\"\n"
+ + "logger_provider:\n"
+ + " processors:\n"
+ + " - batch:\n"
+ + " exporter:\n"
+ + " otlp: {}\n"
+ + " - batch:\n"
+ + " exporter:\n"
+ + " foo: {}\n";
+
+ assertThatThrownBy(
+ () ->
+ ConfigurationFactory.parseAndInterpret(
+ new ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8))))
+ .isInstanceOf(ConfigurationException.class)
+ .hasMessage("Unrecognized log record exporter(s): [foo]");
+ logCapturer.assertContains(
+ "Error encountered interpreting configuration. Closing partially configured components.");
+ logCapturer.assertContains(
+ "Closing io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter");
+ logCapturer.assertContains("Closing io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor");
+ }
+}
diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigTestUtil.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigTestUtil.java
new file mode 100644
index 00000000000..f98bf6e698c
--- /dev/null
+++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigTestUtil.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.sdk.extension.incubator.fileconfig;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+final class FileConfigTestUtil {
+
+ private FileConfigTestUtil() {}
+
+ static String createTempFileWithContent(Path dir, String filename, byte[] content)
+ throws IOException {
+ Path path = dir.resolve(filename);
+ Files.write(path, content);
+ return path.toString();
+ }
+}
diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogLimitsFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogLimitsFactoryTest.java
new file mode 100644
index 00000000000..d59f225d45e
--- /dev/null
+++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogLimitsFactoryTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.LogRecordLimits;
+import io.opentelemetry.sdk.logs.LogLimits;
+import java.util.Collections;
+import org.junit.jupiter.api.Test;
+
+class LogLimitsFactoryTest {
+
+ @Test
+ void create_Null() {
+ assertThat(
+ LogLimitsFactory.getInstance()
+ .create(null, mock(SpiHelper.class), Collections.emptyList()))
+ .isEqualTo(LogLimits.getDefault());
+ }
+
+ @Test
+ void create_Defaults() {
+ assertThat(
+ LogLimitsFactory.getInstance()
+ .create(new LogRecordLimits(), mock(SpiHelper.class), Collections.emptyList()))
+ .isEqualTo(LogLimits.getDefault());
+ }
+
+ @Test
+ void create() {
+ assertThat(
+ LogLimitsFactory.getInstance()
+ .create(
+ new LogRecordLimits()
+ .withAttributeCountLimit(1)
+ .withAttributeValueLengthLimit(2),
+ mock(SpiHelper.class),
+ Collections.emptyList()))
+ .isEqualTo(
+ LogLimits.builder().setMaxNumberOfAttributes(1).setMaxAttributeValueLength(2).build());
+ }
+}
diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordExporterFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordExporterFactoryTest.java
new file mode 100644
index 00000000000..e144e7e0b5e
--- /dev/null
+++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordExporterFactoryTest.java
@@ -0,0 +1,183 @@
+/*
+ * 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.otlp.http.logs.OtlpHttpLogRecordExporter;
+import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter;
+import io.opentelemetry.internal.testing.CleanupExtension;
+import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper;
+import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
+import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
+import io.opentelemetry.sdk.autoconfigure.spi.logs.ConfigurableLogRecordExporterProvider;
+import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Headers;
+import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Otlp;
+import io.opentelemetry.sdk.logs.export.LogRecordExporter;
+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 LogRecordExporterFactoryTest {
+
+ @RegisterExtension
+ static final SelfSignedCertificateExtension serverTls = new SelfSignedCertificateExtension();
+
+ @RegisterExtension
+ static final SelfSignedCertificateExtension clientTls = new SelfSignedCertificateExtension();
+
+ @RegisterExtension CleanupExtension cleanup = new CleanupExtension();
+
+ private SpiHelper spiHelper =
+ SpiHelper.create(LogRecordExporterFactoryTest.class.getClassLoader());
+
+ @Test
+ void create_OtlpDefaults() {
+ spiHelper = spy(spiHelper);
+ List closeables = new ArrayList<>();
+ OtlpGrpcLogRecordExporter expectedExporter = OtlpGrpcLogRecordExporter.getDefault();
+ cleanup.addCloseable(expectedExporter);
+
+ LogRecordExporter exporter =
+ LogRecordExporterFactory.getInstance()
+ .create(
+ new io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model
+ .LogRecordExporter()
+ .withOtlp(new Otlp()),
+ spiHelper,
+ closeables);
+ cleanup.addCloseable(exporter);
+ cleanup.addCloseables(closeables);
+
+ assertThat(exporter.toString()).isEqualTo(expectedExporter.toString());
+
+ ArgumentCaptor configCaptor = ArgumentCaptor.forClass(ConfigProperties.class);
+ verify(spiHelper)
+ .loadConfigurable(
+ eq(ConfigurableLogRecordExporterProvider.class), any(), any(), configCaptor.capture());
+ ConfigProperties configProperties = configCaptor.getValue();
+ assertThat(configProperties.getString("otel.exporter.otlp.logs.protocol")).isNull();
+ assertThat(configProperties.getString("otel.exporter.otlp.logs.endpoint")).isNull();
+ assertThat(configProperties.getMap("otel.exporter.otlp.logs.headers")).isEmpty();
+ assertThat(configProperties.getString("otel.exporter.otlp.logs.compression")).isNull();
+ assertThat(configProperties.getDuration("otel.exporter.otlp.logs.timeout")).isNull();
+ assertThat(configProperties.getString("otel.exporter.otlp.logs.certificate")).isNull();
+ assertThat(configProperties.getString("otel.exporter.otlp.logs.client.key")).isNull();
+ assertThat(configProperties.getString("otel.exporter.otlp.logs.client.certificate")).isNull();
+ }
+
+ @Test
+ void create_OtlpConfigured(@TempDir Path tempDir)
+ throws CertificateEncodingException, IOException {
+ spiHelper = spy(spiHelper);
+ List closeables = new ArrayList<>();
+ OtlpHttpLogRecordExporter expectedExporter =
+ OtlpHttpLogRecordExporter.builder()
+ .setEndpoint("http://example:4318/v1/logs")
+ .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());
+
+ LogRecordExporter exporter =
+ LogRecordExporterFactory.getInstance()
+ .create(
+ new io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model
+ .LogRecordExporter()
+ .withOtlp(
+ new Otlp()
+ .withProtocol("http/protobuf")
+ .withEndpoint("http://example:4318/v1/logs")
+ .withHeaders(
+ new Headers()
+ .withAdditionalProperty("key1", "value1")
+ .withAdditionalProperty("key2", "value2"))
+ .withCompression("gzip")
+ .withTimeout(15_000)
+ .withCertificate(certificatePath)
+ .withClientKey(clientKeyPath)
+ .withClientCertificate(clientCertificatePath)),
+ spiHelper,
+ closeables);
+ cleanup.addCloseable(exporter);
+ cleanup.addCloseables(closeables);
+
+ assertThat(exporter.toString()).isEqualTo(expectedExporter.toString());
+
+ ArgumentCaptor configCaptor = ArgumentCaptor.forClass(ConfigProperties.class);
+ verify(spiHelper)
+ .loadConfigurable(
+ eq(ConfigurableLogRecordExporterProvider.class), any(), any(), configCaptor.capture());
+ ConfigProperties configProperties = configCaptor.getValue();
+ assertThat(configProperties.getString("otel.exporter.otlp.logs.protocol"))
+ .isEqualTo("http/protobuf");
+ assertThat(configProperties.getString("otel.exporter.otlp.logs.endpoint"))
+ .isEqualTo("http://example:4318/v1/logs");
+ assertThat(configProperties.getMap("otel.exporter.otlp.logs.headers"))
+ .isEqualTo(ImmutableMap.of("key1", "value1", "key2", "value2"));
+ assertThat(configProperties.getString("otel.exporter.otlp.logs.compression")).isEqualTo("gzip");
+ assertThat(configProperties.getDuration("otel.exporter.otlp.logs.timeout"))
+ .isEqualTo(Duration.ofSeconds(15));
+ assertThat(configProperties.getString("otel.exporter.otlp.logs.certificate"))
+ .isEqualTo(certificatePath);
+ assertThat(configProperties.getString("otel.exporter.otlp.logs.client.key"))
+ .isEqualTo(clientKeyPath);
+ assertThat(configProperties.getString("otel.exporter.otlp.logs.client.certificate"))
+ .isEqualTo(clientCertificatePath);
+ }
+
+ @Test
+ void create_SpiExporter() {
+ List closeables = new ArrayList<>();
+
+ assertThatThrownBy(
+ () ->
+ LogRecordExporterFactory.getInstance()
+ .create(
+ new io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model
+ .LogRecordExporter()
+ .withAdditionalProperty("test", ImmutableMap.of("key1", "value1")),
+ spiHelper,
+ new ArrayList<>()))
+ .isInstanceOf(ConfigurationException.class)
+ .hasMessage("Unrecognized log record exporter(s): [test]");
+ cleanup.addCloseables(closeables);
+ }
+}
diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordProcessorFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordProcessorFactoryTest.java
new file mode 100644
index 00000000000..043533117b2
--- /dev/null
+++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordProcessorFactoryTest.java
@@ -0,0 +1,176 @@
+/*
+ * 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 com.google.common.collect.ImmutableMap;
+import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter;
+import io.opentelemetry.internal.testing.CleanupExtension;
+import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper;
+import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
+import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchLogRecordProcessor;
+import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordExporter;
+import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordProcessor;
+import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Otlp;
+import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SimpleLogRecordProcessor;
+import java.io.Closeable;
+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;
+
+class LogRecordProcessorFactoryTest {
+
+ @RegisterExtension CleanupExtension cleanup = new CleanupExtension();
+
+ private final SpiHelper spiHelper =
+ SpiHelper.create(LogRecordProcessorFactoryTest.class.getClassLoader());
+
+ @Test
+ void create_Null() {
+ List closeables = new ArrayList<>();
+
+ io.opentelemetry.sdk.logs.LogRecordProcessor processor =
+ LogRecordProcessorFactory.getInstance().create(null, spiHelper, Collections.emptyList());
+ cleanup.addCloseable(processor);
+ cleanup.addCloseables(closeables);
+
+ assertThat(processor.toString())
+ .isEqualTo(io.opentelemetry.sdk.logs.LogRecordProcessor.composite().toString());
+ }
+
+ @Test
+ void create_BatchNullExporter() {
+ List closeables = new ArrayList<>();
+
+ io.opentelemetry.sdk.logs.LogRecordProcessor processor =
+ LogRecordProcessorFactory.getInstance()
+ .create(
+ new LogRecordProcessor().withBatch(new BatchLogRecordProcessor()),
+ spiHelper,
+ Collections.emptyList());
+ cleanup.addCloseable(processor);
+ cleanup.addCloseables(closeables);
+
+ assertThat(processor.toString())
+ .isEqualTo(io.opentelemetry.sdk.logs.LogRecordProcessor.composite().toString());
+ }
+
+ @Test
+ void create_BatchDefaults() {
+ List closeables = new ArrayList<>();
+ io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor expectedProcessor =
+ io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor.builder(
+ OtlpGrpcLogRecordExporter.getDefault())
+ .build();
+ cleanup.addCloseable(expectedProcessor);
+
+ io.opentelemetry.sdk.logs.LogRecordProcessor processor =
+ LogRecordProcessorFactory.getInstance()
+ .create(
+ new LogRecordProcessor()
+ .withBatch(
+ new BatchLogRecordProcessor()
+ .withExporter(new LogRecordExporter().withOtlp(new Otlp()))),
+ spiHelper,
+ closeables);
+ cleanup.addCloseable(processor);
+ cleanup.addCloseables(closeables);
+
+ assertThat(processor.toString()).isEqualTo(expectedProcessor.toString());
+ }
+
+ @Test
+ void create_BatchConfigured() {
+ List closeables = new ArrayList<>();
+ io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor expectedProcessor =
+ io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor.builder(
+ OtlpGrpcLogRecordExporter.getDefault())
+ .setScheduleDelay(Duration.ofMillis(1))
+ .setMaxExportBatchSize(2)
+ .setExporterTimeout(Duration.ofMillis(3))
+ .build();
+ cleanup.addCloseable(expectedProcessor);
+
+ io.opentelemetry.sdk.logs.LogRecordProcessor processor =
+ LogRecordProcessorFactory.getInstance()
+ .create(
+ new LogRecordProcessor()
+ .withBatch(
+ new BatchLogRecordProcessor()
+ .withExporter(new LogRecordExporter().withOtlp(new Otlp()))
+ .withScheduleDelay(1)
+ .withMaxExportBatchSize(2)
+ .withExportTimeout(3)),
+ spiHelper,
+ closeables);
+ cleanup.addCloseable(processor);
+ cleanup.addCloseables(closeables);
+
+ assertThat(processor.toString()).isEqualTo(expectedProcessor.toString());
+ }
+
+ @Test
+ void create_SimpleNullExporter() {
+ List closeables = new ArrayList<>();
+
+ io.opentelemetry.sdk.logs.LogRecordProcessor processor =
+ LogRecordProcessorFactory.getInstance()
+ .create(
+ new LogRecordProcessor().withSimple(new SimpleLogRecordProcessor()),
+ spiHelper,
+ Collections.emptyList());
+ cleanup.addCloseable(processor);
+ cleanup.addCloseables(closeables);
+
+ assertThat(processor.toString())
+ .isEqualTo(io.opentelemetry.sdk.logs.LogRecordProcessor.composite().toString());
+ }
+
+ @Test
+ void create_SimpleConfigured() {
+ List closeables = new ArrayList<>();
+ io.opentelemetry.sdk.logs.LogRecordProcessor expectedProcessor =
+ io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor.create(
+ OtlpGrpcLogRecordExporter.getDefault());
+ cleanup.addCloseable(expectedProcessor);
+
+ io.opentelemetry.sdk.logs.LogRecordProcessor processor =
+ LogRecordProcessorFactory.getInstance()
+ .create(
+ new LogRecordProcessor()
+ .withSimple(
+ new SimpleLogRecordProcessor()
+ .withExporter(new LogRecordExporter().withOtlp(new Otlp()))),
+ spiHelper,
+ closeables);
+ cleanup.addCloseable(processor);
+ cleanup.addCloseables(closeables);
+
+ assertThat(processor.toString()).isEqualTo(expectedProcessor.toString());
+ }
+
+ @Test
+ void create_SpiProcessor() {
+ List closeables = new ArrayList<>();
+
+ assertThatThrownBy(
+ () ->
+ LogRecordProcessorFactory.getInstance()
+ .create(
+ new LogRecordProcessor()
+ .withAdditionalProperty("test", ImmutableMap.of("key1", "value1")),
+ spiHelper,
+ closeables))
+ .isInstanceOf(ConfigurationException.class)
+ .hasMessage("Unrecognized log record processor(s): [test]");
+ cleanup.addCloseables(closeables);
+ }
+}
diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LoggerProviderFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LoggerProviderFactoryTest.java
new file mode 100644
index 00000000000..c455de7ad60
--- /dev/null
+++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LoggerProviderFactoryTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.logs.OtlpGrpcLogRecordExporter;
+import io.opentelemetry.internal.testing.CleanupExtension;
+import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper;
+import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchLogRecordProcessor;
+import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordExporter;
+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.Otlp;
+import io.opentelemetry.sdk.logs.LogLimits;
+import io.opentelemetry.sdk.logs.SdkLoggerProvider;
+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 LoggerProviderFactoryTest {
+
+ @RegisterExtension CleanupExtension cleanup = new CleanupExtension();
+
+ private final SpiHelper spiHelper =
+ SpiHelper.create(LoggerProviderFactoryTest.class.getClassLoader());
+
+ @Test
+ void create_Null() {
+ List closeables = new ArrayList<>();
+ SdkLoggerProvider expectedProvider = SdkLoggerProvider.builder().build();
+ cleanup.addCloseable(expectedProvider);
+
+ SdkLoggerProvider provider =
+ LoggerProviderFactory.getInstance().create(null, spiHelper, closeables).build();
+ cleanup.addCloseable(provider);
+ cleanup.addCloseables(closeables);
+
+ assertThat(provider.toString()).isEqualTo(expectedProvider.toString());
+ }
+
+ @Test
+ void create_Defaults() {
+ List closeables = new ArrayList<>();
+ SdkLoggerProvider expectedProvider = SdkLoggerProvider.builder().build();
+ cleanup.addCloseable(expectedProvider);
+
+ SdkLoggerProvider provider =
+ LoggerProviderFactory.getInstance()
+ .create(new LoggerProvider(), spiHelper, closeables)
+ .build();
+ cleanup.addCloseable(provider);
+ cleanup.addCloseables(closeables);
+
+ assertThat(provider.toString()).isEqualTo(expectedProvider.toString());
+ }
+
+ @Test
+ void create_Configured() {
+ List closeables = new ArrayList<>();
+ SdkLoggerProvider expectedProvider =
+ SdkLoggerProvider.builder()
+ .setLogLimits(
+ () ->
+ LogLimits.builder()
+ .setMaxNumberOfAttributes(1)
+ .setMaxAttributeValueLength(2)
+ .build())
+ .addLogRecordProcessor(
+ io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor.builder(
+ OtlpGrpcLogRecordExporter.getDefault())
+ .build())
+ .build();
+ cleanup.addCloseable(expectedProvider);
+
+ SdkLoggerProvider provider =
+ LoggerProviderFactory.getInstance()
+ .create(
+ new LoggerProvider()
+ .withLimits(
+ new LogRecordLimits()
+ .withAttributeCountLimit(1)
+ .withAttributeValueLengthLimit(2))
+ .withProcessors(
+ Collections.singletonList(
+ new LogRecordProcessor()
+ .withBatch(
+ new BatchLogRecordProcessor()
+ .withExporter(
+ new LogRecordExporter().withOtlp(new Otlp()))))),
+ spiHelper,
+ closeables)
+ .build();
+ cleanup.addCloseable(provider);
+ cleanup.addCloseables(closeables);
+
+ assertThat(provider.toString()).isEqualTo(expectedProvider.toString());
+ }
+}
diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java
new file mode 100644
index 00000000000..b3cae6128ec
--- /dev/null
+++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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 io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter;
+import io.opentelemetry.internal.testing.CleanupExtension;
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper;
+import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
+import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchLogRecordProcessor;
+import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordExporter;
+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.OpenTelemetryConfiguration;
+import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Otlp;
+import io.opentelemetry.sdk.logs.LogLimits;
+import io.opentelemetry.sdk.logs.SdkLoggerProvider;
+import java.io.Closeable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class OpenTelemetryConfigurationFactoryTest {
+
+ @RegisterExtension CleanupExtension cleanup = new CleanupExtension();
+
+ private final SpiHelper spiHelper =
+ SpiHelper.create(OpenTelemetryConfigurationFactoryTest.class.getClassLoader());
+
+ @Test
+ void create_Null() {
+ List closeables = new ArrayList<>();
+ OpenTelemetrySdk expectedSdk = OpenTelemetrySdk.builder().build();
+ cleanup.addCloseable(expectedSdk);
+
+ OpenTelemetrySdk sdk =
+ OpenTelemetryConfigurationFactory.getInstance().create(null, spiHelper, closeables);
+ cleanup.addCloseable(sdk);
+ cleanup.addCloseables(closeables);
+
+ assertThat(sdk.toString()).isEqualTo(expectedSdk.toString());
+ }
+
+ @Test
+ void create_InvalidFileFormat() {
+ List testCases =
+ Arrays.asList(
+ new OpenTelemetryConfiguration(), new OpenTelemetryConfiguration().withFileFormat("1"));
+
+ List closeables = new ArrayList<>();
+ for (OpenTelemetryConfiguration testCase : testCases) {
+ assertThatThrownBy(
+ () ->
+ OpenTelemetryConfigurationFactory.getInstance()
+ .create(testCase, spiHelper, closeables))
+ .isInstanceOf(ConfigurationException.class)
+ .hasMessage("Unsupported file format. Supported formats include: 0.1");
+ cleanup.addCloseables(closeables);
+ }
+ }
+
+ @Test
+ void create_Defaults() {
+ List closeables = new ArrayList<>();
+ OpenTelemetrySdk expectedSdk = OpenTelemetrySdk.builder().build();
+ cleanup.addCloseable(expectedSdk);
+
+ OpenTelemetrySdk sdk =
+ OpenTelemetryConfigurationFactory.getInstance()
+ .create(new OpenTelemetryConfiguration().withFileFormat("0.1"), spiHelper, closeables);
+ cleanup.addCloseable(sdk);
+ cleanup.addCloseables(closeables);
+
+ assertThat(sdk.toString()).isEqualTo(expectedSdk.toString());
+ }
+
+ @Test
+ void create_Configured() {
+ List closeables = new ArrayList<>();
+ OpenTelemetrySdk expectedSdk =
+ OpenTelemetrySdk.builder()
+ .setLoggerProvider(
+ SdkLoggerProvider.builder()
+ .setLogLimits(
+ () ->
+ LogLimits.builder()
+ .setMaxAttributeValueLength(1)
+ .setMaxNumberOfAttributes(2)
+ .build())
+ .addLogRecordProcessor(
+ io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor.builder(
+ OtlpGrpcLogRecordExporter.getDefault())
+ .build())
+ .build())
+ .build();
+ cleanup.addCloseable(expectedSdk);
+
+ OpenTelemetrySdk sdk =
+ OpenTelemetryConfigurationFactory.getInstance()
+ .create(
+ new OpenTelemetryConfiguration()
+ .withFileFormat("0.1")
+ .withLoggerProvider(
+ new LoggerProvider()
+ .withLimits(
+ new LogRecordLimits()
+ .withAttributeValueLengthLimit(1)
+ .withAttributeCountLimit(2))
+ .withProcessors(
+ Collections.singletonList(
+ new LogRecordProcessor()
+ .withBatch(
+ new BatchLogRecordProcessor()
+ .withExporter(
+ new LogRecordExporter()
+ .withOtlp(new Otlp())))))),
+ spiHelper,
+ closeables);
+ cleanup.addCloseable(sdk);
+ cleanup.addCloseables(closeables);
+
+ assertThat(sdk.toString()).isEqualTo(expectedSdk.toString());
+ }
+}
diff --git a/testing-internal/src/main/java/io/opentelemetry/internal/testing/CleanupExtension.java b/testing-internal/src/main/java/io/opentelemetry/internal/testing/CleanupExtension.java
index 2a08e5f414d..77ccf61e329 100644
--- a/testing-internal/src/main/java/io/opentelemetry/internal/testing/CleanupExtension.java
+++ b/testing-internal/src/main/java/io/opentelemetry/internal/testing/CleanupExtension.java
@@ -23,6 +23,11 @@ public void addCloseables(Collection closeables) {
this.closeables.addAll(closeables);
}
+ /** Add a {@link Closeable} to be cleaned up after test completion. */
+ public void addCloseable(Closeable closeable) {
+ this.closeables.add(closeable);
+ }
+
@Override
public void afterEach(ExtensionContext context) {
for (Closeable closeable : closeables) {