Skip to content

Commit

Permalink
Add ExtendedConfigProperties to access complex types from file config…
Browse files Browse the repository at this point in the history
…uration
  • Loading branch information
jack-berg committed Oct 12, 2023
1 parent ae770ec commit af278ba
Show file tree
Hide file tree
Showing 9 changed files with 643 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
public final class DefaultConfigProperties implements ConfigProperties {
public final class DefaultConfigProperties implements ExtendedConfigProperties {

private final Map<String, String> config;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.sdk.autoconfigure.spi.internal;

import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import java.util.List;
import javax.annotation.Nullable;

/**
* An extended version of {@link ConfigProperties} that supports accessing complex types - nested
* maps and arrays of maps. {@link ExtendedConfigProperties} is used as a representation of a map,
* since it has (type safe) accessors for string keys.
*/
public interface ExtendedConfigProperties extends ConfigProperties {

/**
* Returns a map-valued configuration property, represented as {@link ExtendedConfigProperties}.
*
* @return a map-valued configuration property, or {@code null} if {@code name} has not been
* configured.
* @throws io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException if the property is not a
* map
*/
@Nullable
default ExtendedConfigProperties getConfigProperties(String name) {
return null;
}

/**
* Returns a list of map-valued configuration property, represented as {@link
* ExtendedConfigProperties}.
*
* @return a list of map-valued configuration property, or {@code null} if {@code name} has not
* been configured.
* @throws io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException if the property is not a
* list of maps
*/
@Nullable
default List<ExtendedConfigProperties> getListConfigProperties(String name) {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
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.internal.ExtendedConfigProperties;
import io.opentelemetry.sdk.logs.SdkLoggerProvider;
import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder;
import io.opentelemetry.sdk.logs.export.LogRecordExporter;
Expand Down Expand Up @@ -441,12 +442,20 @@ private static AutoConfiguredOpenTelemetrySdk maybeConfigureFromFile(ConfigPrope
try {
Class<?> configurationFactory =
Class.forName("io.opentelemetry.sdk.extension.incubator.fileconfig.ConfigurationFactory");
Method parseAndInterpret =
configurationFactory.getMethod("parseAndInterpret", InputStream.class);
OpenTelemetrySdk sdk = (OpenTelemetrySdk) parseAndInterpret.invoke(null, fis);
Method parse = configurationFactory.getMethod("parse", InputStream.class);
Object model = parse.invoke(null, fis);
Class<?> openTelemetryConfiguration =
Class.forName(
"io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfiguration");
Method interpret = configurationFactory.getMethod("interpret", openTelemetryConfiguration);
OpenTelemetrySdk sdk = (OpenTelemetrySdk) interpret.invoke(null, model);
Method toConfigProperties =
configurationFactory.getMethod("toConfigProperties", openTelemetryConfiguration);
ExtendedConfigProperties configProperties =
(ExtendedConfigProperties) toConfigProperties.invoke(null, model);
// Note: can't access file configuration resource without reflection so setting a dummy
// resource
return AutoConfiguredOpenTelemetrySdk.create(sdk, Resource.getDefault(), config);
return AutoConfiguredOpenTelemetrySdk.create(sdk, Resource.getDefault(), configProperties);
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException e) {
throw new ConfigurationException(
"Error configuring from file. Is opentelemetry-sdk-extension-incubator on the classpath?",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
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.internal.ExtendedConfigProperties;
import io.opentelemetry.sdk.logs.internal.SdkEventEmitterProvider;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
Expand All @@ -44,7 +45,7 @@

class FileConfigurationTest {

@RegisterExtension private static final CleanupExtension cleanup = new CleanupExtension();
@RegisterExtension static final CleanupExtension cleanup = new CleanupExtension();

@TempDir private Path tempDir;
private Path configFilePath;
Expand All @@ -60,7 +61,11 @@ void setup() throws IOException {
+ " processors:\n"
+ " - simple:\n"
+ " exporter:\n"
+ " console: {}\n";
+ " console: {}\n"
+ "other:\n"
+ " str_key: str_value\n"
+ " map_key:\n"
+ " str_key1: str_value1\n";
configFilePath = tempDir.resolve("otel-config.yaml");
Files.write(configFilePath, yaml.getBytes(StandardCharsets.UTF_8));
GlobalOpenTelemetry.resetForTest();
Expand Down Expand Up @@ -175,4 +180,27 @@ void configFile_Error(@TempDir Path tempDir) throws IOException {
.isInstanceOf(ConfigurationException.class)
.hasMessage("Unrecognized span exporter(s): [foo]");
}

@Test
void configFile_ExtendedConfigProperties() {
ConfigProperties config =
DefaultConfigProperties.createFromMap(
Collections.singletonMap("OTEL_CONFIG_FILE", configFilePath.toString()));

AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk =
AutoConfiguredOpenTelemetrySdk.builder().setConfig(config).setResultAsGlobal().build();
OpenTelemetrySdk openTelemetrySdk = autoConfiguredOpenTelemetrySdk.getOpenTelemetrySdk();
cleanup.addCloseable(openTelemetrySdk);

// getConfig() should return ExtendedConfigProperties generic representation of the config file
ConfigProperties config1 = autoConfiguredOpenTelemetrySdk.getConfig();
assertThat(config1).isInstanceOf(ExtendedConfigProperties.class);
ExtendedConfigProperties extendedConfigProps = (ExtendedConfigProperties) config1;
ExtendedConfigProperties otherProps = extendedConfigProps.getConfigProperties("other");
assertThat(otherProps).isNotNull();
assertThat(otherProps.getString("str_key")).isEqualTo("str_value");
ExtendedConfigProperties otherMapKeyProps = otherProps.getConfigProperties("map_key");
assertThat(otherMapKeyProps).isNotNull();
assertThat(otherMapKeyProps.getString("str_key1")).isEqualTo("str_value1");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,24 @@

package io.opentelemetry.sdk.extension.incubator.fileconfig;

import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.Nulls;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
import io.opentelemetry.sdk.autoconfigure.spi.internal.ExtendedConfigProperties;
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.Map;
import java.util.logging.Logger;
import org.snakeyaml.engine.v2.api.Load;
import org.snakeyaml.engine.v2.api.LoadSettings;

/**
* Parses YAML configuration files conforming to the schema in <a
Expand All @@ -26,25 +34,46 @@
*/
public final class ConfigurationFactory {

static final ObjectMapper MAPPER =
new ObjectMapper()
// Create empty object instances for keys which are present but have null values
.setDefaultSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY));

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}
* Combines {@link #parse(InputStream)} and {@link #interpret(OpenTelemetryConfiguration)}
* operations.
*/
public static OpenTelemetrySdk parseAndInterpret(InputStream inputStream) {
OpenTelemetryConfiguration model;
try {
model = ConfigurationReader.parse(inputStream);
model = parse(inputStream);
} catch (RuntimeException e) {
throw new ConfigurationException("Unable to parse inputStream", e);
}

return interpret(model);
}

/** Parse the {@code configuration} YAML and return the {@link OpenTelemetryConfiguration}. */
public static OpenTelemetryConfiguration parse(InputStream configuration) {
LoadSettings settings = LoadSettings.builder().build();
Load yaml = new Load(settings);
Object yamlObj = yaml.loadFromInputStream(configuration);
return MAPPER.convertValue(yamlObj, OpenTelemetryConfiguration.class);
}

/**
* Interpret the {@code model} to create {@link OpenTelemetrySdk} instance corresonding to
* configuration.
*
* @param model the configuration model
* @return the {@link OpenTelemetrySdk}
*/
public static OpenTelemetrySdk interpret(OpenTelemetryConfiguration model) {
List<Closeable> closeables = new ArrayList<>();
try {
return OpenTelemetryConfigurationFactory.getInstance()
Expand All @@ -67,4 +96,18 @@ public static OpenTelemetrySdk parseAndInterpret(InputStream inputStream) {
throw new ConfigurationException("Unexpected configuration error", e);
}
}

/**
* Convert the {@code model} to a generic {@link ExtendedConfigProperties}, which can be used to
* read configuration not part of the model.
*
* @param model the configuration model
* @return a generic {@link ExtendedConfigProperties} representation of the model
*/
public static ExtendedConfigProperties toConfigProperties(OpenTelemetryConfiguration model) {
Map<String, Object> configurationMap =
ConfigurationFactory.MAPPER.convertValue(
model, new TypeReference<Map<String, Object>>() {});
return new FileConfigProperties(configurationMap);
}
}

This file was deleted.

Loading

0 comments on commit af278ba

Please sign in to comment.