Skip to content

Commit

Permalink
Initialize configuration factory (#5687)
Browse files Browse the repository at this point in the history
  • Loading branch information
jack-berg authored Aug 22, 2023
1 parent 917d75d commit 733a2ee
Show file tree
Hide file tree
Showing 18 changed files with 1,308 additions and 3 deletions.
15 changes: 13 additions & 2 deletions sdk-extensions/incubator/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <a
* href="https://github.com/open-telemetry/opentelemetry-configuration">open-telemetry/opentelemetry-configuration</a>
* 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<Closeable> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
Original file line number Diff line number Diff line change
@@ -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<ModelT, ResultT> {

/**
* 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<Closeable> closeables);
}
Original file line number Diff line number Diff line change
@@ -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 extends Closeable> T addAndReturn(List<Closeable> closeables, T closeable) {
closeables.add(closeable);
return closeable;
}

static <T> T assertNotNull(@Nullable T object, String description) {
if (object == null) {
throw new NullPointerException(description + " is null");
}
return object;
}
}
Original file line number Diff line number Diff line change
@@ -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<LogRecordLimits, LogLimits> {

private static final LogLimitsFactory INSTANCE = new LogLimitsFactory();

private LogLimitsFactory() {}

static LogLimitsFactory getInstance() {
return INSTANCE;
}

@Override
public LogLimits create(
@Nullable LogRecordLimits model, SpiHelper spiHelper, List<Closeable> 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();
}
}
Original file line number Diff line number Diff line change
@@ -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<Closeable> 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<String, String> 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<LogRecordExporter> logRecordExporterSpiManager(
ConfigProperties config, SpiHelper spiHelper) {
return spiHelper.loadConfigurable(
ConfigurableLogRecordExporterProvider.class,
ConfigurableLogRecordExporterProvider::getName,
ConfigurableLogRecordExporterProvider::createExporter,
config);
}
}
Loading

0 comments on commit 733a2ee

Please sign in to comment.