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) {