diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 5ecff0a0fe8..9a6f015b098 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -66,7 +66,7 @@ val DEPENDENCIES = listOf( "eu.rekawek.toxiproxy:toxiproxy-java:2.1.7", "io.github.netmikey.logunit:logunit-jul:2.0.0", "io.jaegertracing:jaeger-client:1.8.1", - "io.opentelemetry.contrib:opentelemetry-aws-xray-propagator:1.29.0-alpha", + "io.opentelemetry.contrib:opentelemetry-aws-xray-propagator:1.39.0-alpha", "io.opentelemetry.semconv:opentelemetry-semconv-incubating:1.27.0-alpha", "io.opentelemetry.proto:opentelemetry-proto:1.3.2-alpha", "io.opentracing:opentracing-api:0.33.0", diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpConfigUtil.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpConfigUtil.java index 7fbc5107fd1..1ca885bf5a0 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpConfigUtil.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpConfigUtil.java @@ -10,6 +10,7 @@ import io.opentelemetry.exporter.internal.ExporterBuilderUtil; 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.StructuredConfigProperties; import io.opentelemetry.sdk.common.export.MemoryMode; import io.opentelemetry.sdk.common.export.RetryPolicy; @@ -27,6 +28,8 @@ import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.time.Duration; +import java.util.Collections; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.function.BiConsumer; @@ -97,21 +100,7 @@ public static void configureOtlpExporterBuilder( setEndpoint.accept(endpoint.toString()); } - Map headers = config.getMap("otel.exporter.otlp." + dataType + ".headers"); - if (headers.isEmpty()) { - headers = config.getMap("otel.exporter.otlp.headers"); - } - for (Map.Entry entry : headers.entrySet()) { - String key = entry.getKey(); - String value = entry.getValue(); - try { - // headers are encoded as URL - see - // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#specifying-headers-via-environment-variables - addHeader.accept(key, URLDecoder.decode(value, StandardCharsets.UTF_8.displayName())); - } catch (Exception e) { - throw new ConfigurationException("Cannot decode header value: " + value, e); - } - } + configureOtlpHeaders(config, dataType, addHeader); String compression = config.getString("otel.exporter.otlp." + dataType + ".compression"); if (compression == null) { @@ -190,29 +179,28 @@ public static void configureOtlpExporterBuilder( String protocol = getStructuredConfigOtlpProtocol(config); boolean isHttpProtobuf = protocol.equals(PROTOCOL_HTTP_PROTOBUF); URL endpoint = validateEndpoint(config.getString("endpoint"), isHttpProtobuf); - if (endpoint != null && isHttpProtobuf) { - String path = endpoint.getPath(); - if (!path.endsWith("/")) { - path += "/"; - } - path += signalPath(dataType); - endpoint = createUrl(endpoint, path); - } if (endpoint != null) { setEndpoint.accept(endpoint.toString()); } - StructuredConfigProperties headers = config.getStructured("headers"); + String headerList = config.getString("headers_list"); + if (headerList != null) { + ConfigProperties headersListConfig = + DefaultConfigProperties.createFromMap( + Collections.singletonMap("otel.exporter.otlp.headers", headerList)); + configureOtlpHeaders(headersListConfig, dataType, addHeader); + } + + List headers = config.getStructuredList("headers"); if (headers != null) { - headers - .getPropertyKeys() - .forEach( - header -> { - String value = headers.getString(header); - if (value != null) { - addHeader.accept(header, value); - } - }); + headers.forEach( + header -> { + String name = header.getString("name"); + String value = header.getString("value"); + if (name != null && value != null) { + addHeader.accept(name, value); + } + }); } String compression = config.getString("compression"); @@ -249,6 +237,25 @@ public static void configureOtlpExporterBuilder( ExporterBuilderUtil.configureExporterMemoryMode(config, setMemoryMode); } + private static void configureOtlpHeaders( + ConfigProperties config, String dataType, BiConsumer addHeader) { + Map headers = config.getMap("otel.exporter.otlp." + dataType + ".headers"); + if (headers.isEmpty()) { + headers = config.getMap("otel.exporter.otlp.headers"); + } + for (Map.Entry entry : headers.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + try { + // headers are encoded as URL - see + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#specifying-headers-via-environment-variables + addHeader.accept(key, URLDecoder.decode(value, StandardCharsets.UTF_8.displayName())); + } catch (Exception e) { + throw new ConfigurationException("Cannot decode header value: " + value, e); + } + } + } + /** * Invoke the {@code aggregationTemporalitySelectorConsumer} with the configured {@link * AggregationTemporality}. diff --git a/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/FileConfigurationTest.java b/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/FileConfigurationTest.java index 332c70352ee..e3a9bb3ed10 100644 --- a/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/FileConfigurationTest.java +++ b/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/FileConfigurationTest.java @@ -34,7 +34,8 @@ void configFile(@TempDir Path tempDir) throws IOException { "file_format: \"0.1\"\n" + "resource:\n" + " attributes:\n" - + " service.name: test\n" + + " - name: service.name\n" + + " value: test\n" + "tracer_provider:\n" + " processors:\n" + " - simple:\n" diff --git a/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/FileConfigurationTest.java b/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/FileConfigurationTest.java index c49208aa917..3b3869e5bf6 100644 --- a/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/FileConfigurationTest.java +++ b/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/FileConfigurationTest.java @@ -58,7 +58,8 @@ void setup() throws IOException { "file_format: \"0.1\"\n" + "resource:\n" + " attributes:\n" - + " service.name: test\n" + + " - name: service.name\n" + + " value: test\n" + "tracer_provider:\n" + " processors:\n" + " - simple:\n" @@ -162,7 +163,8 @@ void configFile_Error(@TempDir Path tempDir) throws IOException { "file_format: \"0.1\"\n" + "resource:\n" + " attributes:\n" - + " service.name: test\n" + + " - name: service.name\n" + + " value: test\n" + "tracer_provider:\n" + " processors:\n" + " - simple:\n" diff --git a/sdk-extensions/incubator/build.gradle.kts b/sdk-extensions/incubator/build.gradle.kts index b7c50f4dee1..59acac261ef 100644 --- a/sdk-extensions/incubator/build.gradle.kts +++ b/sdk-extensions/incubator/build.gradle.kts @@ -49,13 +49,14 @@ dependencies { // The sequence of tasks is: // 1. downloadConfigurationSchema - download configuration schema from open-telemetry/opentelemetry-configuration // 2. unzipConfigurationSchema - unzip the configuration schema archive contents to $buildDir/configuration/ -// 3. generateJsonSchema2Pojo - generate java POJOs from the configuration schema -// 4. jsonSchema2PojoPostProcessing - perform various post processing on the generated POJOs, e.g. replace javax.annotation.processing.Generated with javax.annotation.Generated, add @SuppressWarning("rawtypes") annotation -// 5. overwriteJs2p - overwrite original generated classes with versions containing updated @Generated annotation -// 6. deleteJs2pTmp - delete tmp directory +// 3. deleteTypeDescriptions - delete type_descriptions.yaml $buildDir/configuration/schema, which is not part of core schema and causes problems resolving type refs +// 4. generateJsonSchema2Pojo - generate java POJOs from the configuration schema +// 5. jsonSchema2PojoPostProcessing - perform various post processing on the generated POJOs, e.g. replace javax.annotation.processing.Generated with javax.annotation.Generated, add @SuppressWarning("rawtypes") annotation +// 6. overwriteJs2p - overwrite original generated classes with versions containing updated @Generated annotation +// 7. deleteJs2pTmp - delete tmp directory // ... proceed with normal sourcesJar, compileJava, etc -val configurationTag = "0.1.0" +val configurationTag = "0.3.0" val configurationRef = "refs/tags/v$configurationTag" // Replace with commit SHA to point to experiment with a specific commit val configurationRepoZip = "https://github.com/open-telemetry/opentelemetry-configuration/archive/$configurationRef.zip" val buildDirectory = layout.buildDirectory.asFile.get() @@ -78,6 +79,11 @@ val unzipConfigurationSchema by tasks.registering(Copy::class) { into("$buildDirectory/configuration/") } +val deleteTypeDescriptions by tasks.registering(Delete::class) { + dependsOn(unzipConfigurationSchema) + delete("$buildDirectory/configuration/schema/type_descriptions.yaml") +} + jsonSchema2Pojo { sourceFiles = setOf(file("$buildDirectory/configuration/schema")) targetDirectory = file("$buildDirectory/generated/sources/js2p/java/main") @@ -106,7 +112,7 @@ jsonSchema2Pojo { } val generateJsonSchema2Pojo = tasks.getByName("generateJsonSchema2Pojo") -generateJsonSchema2Pojo.dependsOn(unzipConfigurationSchema) +generateJsonSchema2Pojo.dependsOn(deleteTypeDescriptions) val jsonSchema2PojoPostProcessing by tasks.registering(Copy::class) { dependsOn(generateJsonSchema2Pojo) diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributeListFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributeListFactory.java new file mode 100644 index 00000000000..c3bba8925b3 --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributeListFactory.java @@ -0,0 +1,140 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig; + +import static java.util.stream.Collectors.toList; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AttributeNameValueModel; +import java.io.Closeable; +import java.util.List; +import javax.annotation.Nullable; + +final class AttributeListFactory + implements Factory, io.opentelemetry.api.common.Attributes> { + + private static final AttributeListFactory INSTANCE = new AttributeListFactory(); + + private AttributeListFactory() {} + + static AttributeListFactory getInstance() { + return INSTANCE; + } + + @Override + public io.opentelemetry.api.common.Attributes create( + List model, SpiHelper spiHelper, List closeables) { + AttributesBuilder builder = io.opentelemetry.api.common.Attributes.builder(); + + for (AttributeNameValueModel nameValueModel : model) { + addToBuilder(nameValueModel, builder); + } + + return builder.build(); + } + + private static void addToBuilder( + AttributeNameValueModel nameValueModel, AttributesBuilder builder) { + String name = FileConfigUtil.requireNonNull(nameValueModel.getName(), "attribute name"); + Object value = FileConfigUtil.requireNonNull(nameValueModel.getValue(), "attribute value"); + AttributeNameValueModel.Type type = nameValueModel.getType(); + if (type == null) { + type = AttributeNameValueModel.Type.STRING; + } + switch (type) { + case STRING: + if (value instanceof String) { + builder.put(name, (String) value); + return; + } + break; + case BOOL: + if (value instanceof Boolean) { + builder.put(name, (boolean) value); + return; + } + break; + case INT: + if ((value instanceof Integer) || (value instanceof Long)) { + builder.put(name, ((Number) value).longValue()); + return; + } + break; + case DOUBLE: + if (value instanceof Number) { + builder.put(name, ((Number) value).doubleValue()); + return; + } + break; + case STRING_ARRAY: + List stringList = checkListOfType(value, String.class); + if (stringList != null) { + builder.put(AttributeKey.stringArrayKey(name), stringList); + return; + } + break; + case BOOL_ARRAY: + List boolList = checkListOfType(value, Boolean.class); + if (boolList != null) { + builder.put(AttributeKey.booleanArrayKey(name), boolList); + return; + } + break; + case INT_ARRAY: + List longList = checkListOfType(value, Long.class); + if (longList != null) { + builder.put(AttributeKey.longArrayKey(name), longList); + return; + } + List intList = checkListOfType(value, Integer.class); + if (intList != null) { + builder.put( + AttributeKey.longArrayKey(name), + intList.stream().map(i -> (long) i).collect(toList())); + return; + } + break; + case DOUBLE_ARRAY: + List doubleList = checkListOfType(value, Double.class); + if (doubleList != null) { + builder.put(AttributeKey.doubleArrayKey(name), doubleList); + return; + } + List floatList = checkListOfType(value, Float.class); + if (floatList != null) { + builder.put( + AttributeKey.doubleArrayKey(name), + floatList.stream().map(i -> (double) i).collect(toList())); + return; + } + break; + } + throw new ConfigurationException( + "Error processing attribute with name \"" + + name + + "\": value did not match type " + + type.name()); + } + + @SuppressWarnings("unchecked") + @Nullable + private static List checkListOfType(Object value, Class expectedType) { + if (!(value instanceof List)) { + return null; + } + List list = (List) value; + if (list.isEmpty()) { + return null; + } + if (!list.stream().allMatch(entry -> expectedType.isAssignableFrom(entry.getClass()))) { + return null; + } + return (List) value; + } +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributesFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributesFactory.java deleted file mode 100644 index a45031a85f3..00000000000 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributesFactory.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.extension.incubator.fileconfig; - -import static io.opentelemetry.api.common.AttributeKey.stringKey; - -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; -import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AttributesModel; -import java.io.Closeable; -import java.util.List; - -final class AttributesFactory - implements Factory { - - private static final AttributesFactory INSTANCE = new AttributesFactory(); - - private AttributesFactory() {} - - static AttributesFactory getInstance() { - return INSTANCE; - } - - @Override - public io.opentelemetry.api.common.Attributes create( - AttributesModel model, SpiHelper spiHelper, List closeables) { - AttributesBuilder builder = io.opentelemetry.api.common.Attributes.builder(); - - String serviceName = model.getServiceName(); - if (serviceName != null) { - builder.put(stringKey("service.name"), serviceName); - } - - model - .getAdditionalProperties() - .forEach( - (key, value) -> { - if (value == null) { - throw new ConfigurationException( - "Error processing attribute with key \"" + key + "\": unexpected null value"); - } - if (value instanceof String) { - builder.put(key, (String) value); - return; - } - if (value instanceof Integer) { - builder.put(key, (int) value); - return; - } - if (value instanceof Long) { - builder.put(key, (long) value); - return; - } - if (value instanceof Double) { - builder.put(key, (double) value); - return; - } - if (value instanceof Float) { - builder.put(key, (float) value); - return; - } - if (value instanceof Boolean) { - builder.put(key, (boolean) value); - return; - } - if (value instanceof List) { - List values = (List) value; - if (values.isEmpty()) { - return; - } - Object first = values.get(0); - if (first instanceof String) { - checkAllEntriesOfType(key, values, String.class); - builder.put( - AttributeKey.stringArrayKey(key), - values.stream().map(obj -> (String) obj).toArray(String[]::new)); - return; - } - if (first instanceof Long) { - checkAllEntriesOfType(key, values, Long.class); - builder.put( - AttributeKey.longArrayKey(key), - values.stream().map(obj -> (long) obj).toArray(Long[]::new)); - return; - } - if (first instanceof Integer) { - checkAllEntriesOfType(key, values, Integer.class); - builder.put( - AttributeKey.longArrayKey(key), - values.stream().map(obj -> Long.valueOf((int) obj)).toArray(Long[]::new)); - return; - } - if (first instanceof Double) { - checkAllEntriesOfType(key, values, Double.class); - builder.put( - AttributeKey.doubleArrayKey(key), - values.stream().map(obj -> (double) obj).toArray(Double[]::new)); - return; - } - if (first instanceof Float) { - checkAllEntriesOfType(key, values, Float.class); - builder.put( - AttributeKey.doubleArrayKey(key), - values.stream() - .map(obj -> Double.valueOf((float) obj)) - .toArray(Double[]::new)); - return; - } - if (first instanceof Boolean) { - checkAllEntriesOfType(key, values, Boolean.class); - builder.put( - AttributeKey.booleanArrayKey(key), - values.stream().map(obj -> (Boolean) obj).toArray(Boolean[]::new)); - return; - } - } - throw new ConfigurationException( - "Error processing attribute with key \"" - + key - + "\": unrecognized value type " - + value.getClass().getName()); - }); - - return builder.build(); - } - - private static void checkAllEntriesOfType(String key, List values, Class expectedType) { - values.forEach( - value -> { - if (value == null) { - throw new ConfigurationException( - "Error processing attribute with key \"" - + key - + "\": unexpected null element in value"); - } - if (!expectedType.isAssignableFrom(value.getClass())) { - throw new ConfigurationException( - "Error processing attribute with key \"" - + key - + "\": expected value entries to be of type " - + expectedType - + " but found entry with type " - + value.getClass()); - } - }); - } -} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfiguration.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfiguration.java index 5b1b3dd32f0..76cf9cc1fbe 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfiguration.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfiguration.java @@ -49,7 +49,7 @@ public final class FileConfiguration { private static final Logger logger = Logger.getLogger(FileConfiguration.class.getName()); private static final Pattern ENV_VARIABLE_REFERENCE = - Pattern.compile("\\$\\{([a-zA-Z_][a-zA-Z0-9_]*)}"); + Pattern.compile("\\$\\{([a-zA-Z_][a-zA-Z0-9_]*)(:-([^\n]*))?\\}"); private static final ComponentLoader DEFAULT_COMPONENT_LOADER = SpiHelper.serviceComponentLoader(FileConfiguration.class.getClassLoader()); @@ -313,7 +313,12 @@ private Object constructValueObject(Node node) { ScalarStyle scalarStyle = ((ScalarNode) node).getScalarStyle(); do { MatchResult matchResult = matcher.toMatchResult(); - String replacement = environmentVariables.getOrDefault(matcher.group(1), ""); + String envVarKey = matcher.group(1); + String defaultValue = matcher.group(3); + if (defaultValue == null) { + defaultValue = ""; + } + String replacement = environmentVariables.getOrDefault(envVarKey, defaultValue); newVal.append(val, offset, matchResult.start()).append(replacement); offset = matchResult.end(); } while (matcher.find()); 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 index 05be1719dbb..e006befe0f7 100644 --- 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 @@ -9,6 +9,7 @@ import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ConsoleModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordExporterModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OtlpModel; import io.opentelemetry.sdk.logs.export.LogRecordExporter; @@ -34,6 +35,11 @@ public LogRecordExporter create( model.getAdditionalProperties().put("otlp", otlpModel); } + ConsoleModel consoleModel = model.getConsole(); + if (consoleModel != null) { + model.getAdditionalProperties().put("console", consoleModel); + } + if (!model.getAdditionalProperties().isEmpty()) { Map additionalProperties = model.getAdditionalProperties(); if (additionalProperties.size() > 1) { diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricExporterFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricExporterFactory.java index cfda507de32..6d9616f8524 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricExporterFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricExporterFactory.java @@ -9,14 +9,14 @@ import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; -import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricExporterModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OtlpMetricModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PushMetricExporterModel; import io.opentelemetry.sdk.metrics.export.MetricExporter; import java.io.Closeable; import java.util.List; import java.util.Map; -final class MetricExporterFactory implements Factory { +final class MetricExporterFactory implements Factory { private static final MetricExporterFactory INSTANCE = new MetricExporterFactory(); @@ -28,7 +28,7 @@ static MetricExporterFactory getInstance() { @Override public MetricExporter create( - MetricExporterModel model, SpiHelper spiHelper, List closeables) { + PushMetricExporterModel model, SpiHelper spiHelper, List closeables) { OtlpMetricModel otlpModel = model.getOtlp(); if (otlpModel != null) { model.getAdditionalProperties().put("otlp", otlpModel); @@ -38,10 +38,6 @@ public MetricExporter create( model.getAdditionalProperties().put("console", model.getConsole()); } - if (model.getPrometheus() != null) { - throw new ConfigurationException("prometheus exporter not supported in this context"); - } - if (!model.getAdditionalProperties().isEmpty()) { Map additionalProperties = model.getAdditionalProperties(); if (additionalProperties.size() > 1) { diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactory.java index 1041d50ec6c..c30ac774b60 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactory.java @@ -9,11 +9,12 @@ import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; -import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricExporterModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricReaderModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PeriodicMetricReaderModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PrometheusModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PullMetricExporterModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PullMetricReaderModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PushMetricExporterModel; import io.opentelemetry.sdk.metrics.export.MetricReader; import io.opentelemetry.sdk.metrics.export.PeriodicMetricReaderBuilder; import java.io.Closeable; @@ -35,7 +36,7 @@ public MetricReader create( MetricReaderModel model, SpiHelper spiHelper, List closeables) { PeriodicMetricReaderModel periodicModel = model.getPeriodic(); if (periodicModel != null) { - MetricExporterModel exporterModel = + PushMetricExporterModel exporterModel = requireNonNull(periodicModel.getExporter(), "periodic metric reader exporter"); io.opentelemetry.sdk.metrics.export.MetricExporter metricExporter = MetricExporterFactory.getInstance().create(exporterModel, spiHelper, closeables); @@ -50,7 +51,7 @@ public MetricReader create( PullMetricReaderModel pullModel = model.getPull(); if (pullModel != null) { - MetricExporterModel exporterModel = + PullMetricExporterModel exporterModel = requireNonNull(pullModel.getExporter(), "pull metric reader exporter"); PrometheusModel prometheusModel = exporterModel.getPrometheus(); if (prometheusModel != null) { diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ResourceFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ResourceFactory.java index bb5540268a8..8f26e6a47e7 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ResourceFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ResourceFactory.java @@ -5,19 +5,28 @@ package io.opentelemetry.sdk.extension.incubator.fileconfig; +import static io.opentelemetry.sdk.internal.GlobUtil.toGlobPatternPredicate; + +import io.opentelemetry.sdk.autoconfigure.ResourceConfiguration; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; import io.opentelemetry.sdk.autoconfigure.spi.Ordered; import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; -import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AttributesModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AttributeNameValueModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.DetectorAttributesModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.DetectorsModel; import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.resources.ResourceBuilder; import java.io.Closeable; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.function.Predicate; import java.util.stream.Collectors; +import javax.annotation.Nullable; final class ResourceFactory implements Factory< @@ -41,23 +50,39 @@ public Resource create( io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ResourceModel model, SpiHelper spiHelper, List closeables) { - Resource result = Resource.getDefault(); + ResourceBuilder builder = Resource.getDefault().toBuilder(); + ResourceBuilder detectedResourceBuilder = Resource.builder(); List resourceDetectorResources = loadFromResourceDetectors(spiHelper); for (Resource resourceProviderResource : resourceDetectorResources) { - result = result.merge(resourceProviderResource); + detectedResourceBuilder.putAll(resourceProviderResource); } + Predicate detectorAttributeFilter = detectorAttributeFilter(model.getDetectors()); + builder + .putAll( + detectedResourceBuilder.build().getAttributes().toBuilder() + .removeIf(attributeKey -> !detectorAttributeFilter.test(attributeKey.getKey())) + .build()) + .build(); - AttributesModel attributesModel = model.getAttributes(); - if (attributesModel != null) { - result = - result.toBuilder() - .putAll( - AttributesFactory.getInstance().create(attributesModel, spiHelper, closeables)) - .build(); + String attributeList = model.getAttributesList(); + if (attributeList != null) { + builder.putAll( + ResourceConfiguration.createEnvironmentResource( + DefaultConfigProperties.createFromMap( + Collections.singletonMap("otel.resource.attributes", attributeList)))); } - return result; + List attributeNameValueModel = model.getAttributes(); + if (attributeNameValueModel != null) { + builder + .putAll( + AttributeListFactory.getInstance() + .create(attributeNameValueModel, spiHelper, closeables)) + .build(); + } + + return builder.build(); } /** @@ -115,4 +140,54 @@ private int order() { return order; } } + + private static boolean matchAll(String attributeKey) { + return true; + } + + private static Predicate detectorAttributeFilter( + @Nullable DetectorsModel detectorsModel) { + if (detectorsModel == null) { + return ResourceFactory::matchAll; + } + DetectorAttributesModel detectorAttributesModel = detectorsModel.getAttributes(); + if (detectorAttributesModel == null) { + return ResourceFactory::matchAll; + } + List included = detectorAttributesModel.getIncluded(); + List excluded = detectorAttributesModel.getExcluded(); + if (included == null && excluded == null) { + return ResourceFactory::matchAll; + } + if (included == null) { + return excludedPredicate(excluded); + } + if (excluded == null) { + return includedPredicate(included); + } + return includedPredicate(included).and(excludedPredicate(excluded)); + } + + /** + * Returns a predicate which matches strings matching any of the {@code included} glob patterns. + */ + private static Predicate includedPredicate(List included) { + Predicate result = attributeKey -> false; + for (String include : included) { + result = result.or(toGlobPatternPredicate(include)); + } + return result; + } + + /** + * Returns a predicate which matches strings NOT matching any of the {@code excluded} glob + * patterns. + */ + private static Predicate excludedPredicate(List excluded) { + Predicate result = attributeKey -> true; + for (String exclude : excluded) { + result = result.and(toGlobPatternPredicate(exclude).negate()); + } + return result; + } } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ViewFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ViewFactory.java index fecc4a81c59..38ad79d7669 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ViewFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ViewFactory.java @@ -6,12 +6,15 @@ package io.opentelemetry.sdk.extension.incubator.fileconfig; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.IncludeExcludeModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.StreamModel; import io.opentelemetry.sdk.metrics.View; import io.opentelemetry.sdk.metrics.ViewBuilder; import java.io.Closeable; import java.util.HashSet; import java.util.List; +import java.util.Set; +import javax.annotation.Nullable; final class ViewFactory implements Factory { @@ -32,8 +35,9 @@ public View create(StreamModel model, SpiHelper spiHelper, List close if (model.getDescription() != null) { builder.setDescription(model.getDescription()); } - if (model.getAttributeKeys() != null) { - builder.setAttributeFilter(new HashSet<>(model.getAttributeKeys())); + IncludeExcludeModel attributeKeys = model.getAttributeKeys(); + if (attributeKeys != null) { + addAttributeKeyFilter(builder, attributeKeys.getIncluded(), attributeKeys.getExcluded()); } if (model.getAggregation() != null) { builder.setAggregation( @@ -41,4 +45,25 @@ public View create(StreamModel model, SpiHelper spiHelper, List close } return builder.build(); } + + private static void addAttributeKeyFilter( + ViewBuilder builder, @Nullable List included, @Nullable List excluded) { + if (included == null && excluded == null) { + return; + } + if (included == null) { + Set excludedKeys = new HashSet<>(excluded); + // TODO: set predicate with useful toString implementation + builder.setAttributeFilter(attributeKey -> !excludedKeys.contains(attributeKey)); + return; + } + if (excluded == null) { + Set includedKeys = new HashSet<>(included); + builder.setAttributeFilter(includedKeys); + return; + } + Set includedKeys = new HashSet<>(included); + excluded.forEach(includedKeys::remove); + builder.setAttributeFilter(includedKeys); + } } diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributeListFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributeListFactoryTest.java new file mode 100644 index 00000000000..59dd61c03cf --- /dev/null +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributeListFactoryTest.java @@ -0,0 +1,140 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AttributeNameValueModel; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class AttributeListFactoryTest { + + @ParameterizedTest + @MethodSource("invalidAttributes") + void create_InvalidAttributes(List model, String expectedMessage) { + assertThatThrownBy( + () -> + AttributeListFactory.getInstance() + .create(model, mock(SpiHelper.class), Collections.emptyList())) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining(expectedMessage); + } + + private static Stream invalidAttributes() { + return Stream.of( + Arguments.of( + Collections.singletonList(new AttributeNameValueModel().withName("key")), + "attribute value is required but is null"), + Arguments.of( + Collections.singletonList( + new AttributeNameValueModel().withName("key").withValue(new Object())), + "Error processing attribute with name \"key\": value did not match type STRING"), + Arguments.of( + Collections.singletonList( + new AttributeNameValueModel() + .withName("key") + .withType(AttributeNameValueModel.Type.INT) + .withValue(Arrays.asList(1L, 1))), + "Error processing attribute with name \"key\": value did not match type INT"), + Arguments.of( + Collections.singletonList( + new AttributeNameValueModel() + .withName("key") + .withType(AttributeNameValueModel.Type.INT) + .withValue(true)), + "Error processing attribute with name \"key\": value did not match type INT")); + } + + @Test + void create() { + Attributes expectedAttributes = + Attributes.builder() + .put("service.name", "my-service") + .put("strKey", "val") + .put("longKey", 1L) + .put("intKey", 2) + .put("doubleKey", 1.0d) + .put("floatKey", 2.0f) + .put("boolKey", true) + .put("strArrKey", "val1", "val2") + .put("longArrKey", 1L, 2L) + .put("intArrKey", 1, 2) + .put("doubleArrKey", 1.0d, 2.0d) + .put("floatArrKey", 1.0f, 2.0f) + .put("boolArrKey", true, false) + .build(); + assertThat( + AttributeListFactory.getInstance() + .create( + Arrays.asList( + new AttributeNameValueModel() + .withName("service.name") + .withValue("my-service"), + new AttributeNameValueModel() + .withName("strKey") + .withValue("val") + .withType(AttributeNameValueModel.Type.STRING), + new AttributeNameValueModel() + .withName("longKey") + .withValue(1L) + .withType(AttributeNameValueModel.Type.INT), + new AttributeNameValueModel() + .withName("intKey") + .withValue(2) + .withType(AttributeNameValueModel.Type.INT), + new AttributeNameValueModel() + .withName("doubleKey") + .withValue(1.0d) + .withType(AttributeNameValueModel.Type.DOUBLE), + new AttributeNameValueModel() + .withName("floatKey") + .withValue(2.0f) + .withType(AttributeNameValueModel.Type.DOUBLE), + new AttributeNameValueModel() + .withName("boolKey") + .withValue(true) + .withType(AttributeNameValueModel.Type.BOOL), + new AttributeNameValueModel() + .withName("strArrKey") + .withValue(Arrays.asList("val1", "val2")) + .withType(AttributeNameValueModel.Type.STRING_ARRAY), + new AttributeNameValueModel() + .withName("longArrKey") + .withValue(Arrays.asList(1L, 2L)) + .withType(AttributeNameValueModel.Type.INT_ARRAY), + new AttributeNameValueModel() + .withName("intArrKey") + .withValue(Arrays.asList(1, 2)) + .withType(AttributeNameValueModel.Type.INT_ARRAY), + new AttributeNameValueModel() + .withName("doubleArrKey") + .withValue(Arrays.asList(1.0d, 2.0d)) + .withType(AttributeNameValueModel.Type.DOUBLE_ARRAY), + new AttributeNameValueModel() + .withName("floatArrKey") + .withValue(Arrays.asList(1.0f, 2.0f)) + .withType(AttributeNameValueModel.Type.DOUBLE_ARRAY), + new AttributeNameValueModel() + .withName("boolArrKey") + .withValue(Arrays.asList(true, false)) + .withType(AttributeNameValueModel.Type.BOOL_ARRAY)), + mock(SpiHelper.class), + Collections.emptyList())) + .isEqualTo(expectedAttributes); + } +} diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributesFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributesFactoryTest.java deleted file mode 100644 index 5bd8e42a540..00000000000 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributesFactoryTest.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.extension.incubator.fileconfig; - -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; - -import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; -import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AttributesModel; -import java.util.Arrays; -import java.util.Collections; -import java.util.stream.Stream; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -class AttributesFactoryTest { - - @ParameterizedTest - @MethodSource("invalidAttributes") - void create_InvalidAttributes(AttributesModel model, String expectedMessage) { - assertThatThrownBy( - () -> - AttributesFactory.getInstance() - .create(model, mock(SpiHelper.class), Collections.emptyList())) - .isInstanceOf(ConfigurationException.class) - .hasMessageContaining(expectedMessage); - } - - private static Stream invalidAttributes() { - return Stream.of( - Arguments.of( - new AttributesModel().withAdditionalProperty("key", null), - "Error processing attribute with key \"key\": unexpected null value"), - Arguments.of( - new AttributesModel().withAdditionalProperty("key", new Object()), - "Error processing attribute with key \"key\": unrecognized value type java.lang.Object"), - Arguments.of( - new AttributesModel().withAdditionalProperty("key", Arrays.asList(1L, 1)), - "Error processing attribute with key \"key\": expected value entries to be of type class java.lang.Long but found entry with type class java.lang.Integer"), - Arguments.of( - new AttributesModel().withAdditionalProperty("key", Arrays.asList(1L, null)), - "Error processing attribute with key \"key\": unexpected null element in value")); - } - - @Test - void create() { - assertThat( - AttributesFactory.getInstance() - .create( - new AttributesModel() - .withServiceName("my-service") - .withAdditionalProperty("strKey", "val") - .withAdditionalProperty("longKey", 1L) - .withAdditionalProperty("intKey", 2) - .withAdditionalProperty("doubleKey", 1.0d) - .withAdditionalProperty("floatKey", 2.0f) - .withAdditionalProperty("boolKey", true) - .withAdditionalProperty("strArrKey", Arrays.asList("val1", "val2")) - .withAdditionalProperty("longArrKey", Arrays.asList(1L, 2L)) - .withAdditionalProperty("intArrKey", Arrays.asList(1, 2)) - .withAdditionalProperty("doubleArrKey", Arrays.asList(1.0d, 2.0d)) - .withAdditionalProperty("floatArrKey", Arrays.asList(1.0f, 2.0f)) - .withAdditionalProperty("boolArrKey", Arrays.asList(true, false)) - .withAdditionalProperty("emptyArrKey", Collections.emptyList()), - mock(SpiHelper.class), - Collections.emptyList())) - .isEqualTo( - io.opentelemetry.api.common.Attributes.builder() - .put("service.name", "my-service") - .put("strKey", "val") - .put("longKey", 1L) - .put("intKey", 2) - .put("doubleKey", 1.0d) - .put("floatKey", 2.0f) - .put("boolKey", true) - .put("strArrKey", "val1", "val2") - .put("longArrKey", 1L, 2L) - .put("intArrKey", 1, 2) - .put("doubleArrKey", 1.0d, 2.0d) - .put("floatArrKey", 1.0f, 2.0f) - .put("boolArrKey", true, false) - .build()); - } -} diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigurationCreateTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigurationCreateTest.java index 51f70d930ec..d47ae14eb9a 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigurationCreateTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigurationCreateTest.java @@ -86,10 +86,7 @@ void parseAndCreate_Examples(@TempDir Path tempDir) "client_key: .*\n", "client_key: " + clientKeyPath + System.lineSeparator()) .replaceAll( "client_certificate: .*\n", - "client_certificate: " + clientCertificatePath + System.lineSeparator()) - // TODO: remove once ComponentProvider SPI implemented in - // https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/aws-xray-propagator - .replaceAll("xray,", ""); + "client_certificate: " + clientCertificatePath + System.lineSeparator()); InputStream is = new ByteArrayInputStream(rewrittenExampleContent.getBytes(StandardCharsets.UTF_8)); diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigurationParseTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigurationParseTest.java index 0c03bdbd751..81aa6f4487e 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigurationParseTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigurationParseTest.java @@ -13,30 +13,45 @@ import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AlwaysOffModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AlwaysOnModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AttributeLimitsModel; -import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AttributesModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AttributeNameValueModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchLogRecordProcessorModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchSpanProcessorModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ClientModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ConsoleModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.DetectorAttributesModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.DetectorsModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExplicitBucketHistogramModel; -import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.HeadersModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.GeneralInstrumentationModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.HttpModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.IncludeExcludeModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.InstrumentationModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LanguageSpecificInstrumentationModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordExporterModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordLimitsModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordProcessorModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LoggerProviderModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MeterProviderModel; -import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricExporterModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricProducerModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricReaderModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.NameStringValuePairModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpencensusModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OtlpMetricModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OtlpModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ParentBasedModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PeerModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PeriodicMetricReaderModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PrometheusModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PropagatorModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PullMetricExporterModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PullMetricReaderModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PushMetricExporterModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ResourceModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SamplerModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SelectorModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ServerModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ServiceMappingModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SimpleLogRecordProcessorModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SimpleSpanProcessorModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SpanExporterModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SpanLimitsModel; @@ -83,7 +98,51 @@ void parse_KitchenSinkExampleFile() throws IOException { // General config ResourceModel resource = new ResourceModel() - .withAttributes(new AttributesModel().withServiceName("unknown_service")); + .withAttributes( + Arrays.asList( + new AttributeNameValueModel() + .withName("service.name") + .withValue("unknown_service"), + new AttributeNameValueModel() + .withName("string_key") + .withValue("value") + .withType(AttributeNameValueModel.Type.STRING), + new AttributeNameValueModel() + .withName("bool_key") + .withValue(true) + .withType(AttributeNameValueModel.Type.BOOL), + new AttributeNameValueModel() + .withName("int_key") + .withValue(1) + .withType(AttributeNameValueModel.Type.INT), + new AttributeNameValueModel() + .withName("double_key") + .withValue(1.1) + .withType(AttributeNameValueModel.Type.DOUBLE), + new AttributeNameValueModel() + .withName("string_array_key") + .withValue(Arrays.asList("value1", "value2")) + .withType(AttributeNameValueModel.Type.STRING_ARRAY), + new AttributeNameValueModel() + .withName("bool_array_key") + .withValue(Arrays.asList(true, false)) + .withType(AttributeNameValueModel.Type.BOOL_ARRAY), + new AttributeNameValueModel() + .withName("int_array_key") + .withValue(Arrays.asList(1, 2)) + .withType(AttributeNameValueModel.Type.INT_ARRAY), + new AttributeNameValueModel() + .withName("double_array_key") + .withValue(Arrays.asList(1.1, 2.2)) + .withType(AttributeNameValueModel.Type.DOUBLE_ARRAY))) + .withAttributesList("service.namespace=my-namespace,service.version=1.0.0") + .withDetectors( + new DetectorsModel() + .withAttributes( + new DetectorAttributesModel() + .withIncluded(Collections.singletonList("process.*")) + .withExcluded(Collections.singletonList("process.command_args")))) + .withSchemaUrl("https://opentelemetry.io/schemas/1.16.0"); expected.withResource(resource); AttributeLimitsModel attributeLimits = @@ -138,15 +197,19 @@ void parse_KitchenSinkExampleFile() throws IOException { .withOtlp( new OtlpModel() .withProtocol("http/protobuf") - .withEndpoint("http://localhost:4318") + .withEndpoint("http://localhost:4318/v1/traces") .withCertificate("/app/cert.pem") .withClientKey("/app/cert.pem") .withClientCertificate("/app/cert.pem") .withHeaders( - new HeadersModel() - .withAdditionalProperty("api-key", "1234")) + Collections.singletonList( + new NameStringValuePairModel() + .withName("api-key") + .withValue("1234"))) + .withHeadersList("api-key=1234") .withCompression("gzip") - .withTimeout(10_000)))); + .withTimeout(10_000) + .withInsecure(false)))); SpanProcessorModel spanProcessor2 = new SpanProcessorModel() .withBatch( @@ -174,7 +237,7 @@ void parse_KitchenSinkExampleFile() throws IOException { new LogRecordLimitsModel().withAttributeValueLengthLimit(4096).withAttributeCountLimit(128); loggerProvider.withLimits(logRecordLimits); - LogRecordProcessorModel logRecordProcessor = + LogRecordProcessorModel logRecordProcessor1 = new LogRecordProcessorModel() .withBatch( new BatchLogRecordProcessorModel() @@ -187,16 +250,25 @@ void parse_KitchenSinkExampleFile() throws IOException { .withOtlp( new OtlpModel() .withProtocol("http/protobuf") - .withEndpoint("http://localhost:4318") + .withEndpoint("http://localhost:4318/v1/logs") .withCertificate("/app/cert.pem") .withClientKey("/app/cert.pem") .withClientCertificate("/app/cert.pem") .withHeaders( - new HeadersModel() - .withAdditionalProperty("api-key", "1234")) + Collections.singletonList( + new NameStringValuePairModel() + .withName("api-key") + .withValue("1234"))) + .withHeadersList("api-key=1234") .withCompression("gzip") - .withTimeout(10_000)))); - loggerProvider.withProcessors(Collections.singletonList(logRecordProcessor)); + .withTimeout(10_000) + .withInsecure(false)))); + LogRecordProcessorModel logRecordProcessor2 = + new LogRecordProcessorModel() + .withSimple( + new SimpleLogRecordProcessorModel() + .withExporter(new LogRecordExporterModel().withConsole(new ConsoleModel()))); + loggerProvider.withProcessors(Arrays.asList(logRecordProcessor1, logRecordProcessor2)); expected.withLoggerProvider(loggerProvider); // end LoggerProvider config @@ -209,9 +281,22 @@ void parse_KitchenSinkExampleFile() throws IOException { .withPull( new PullMetricReaderModel() .withExporter( - new MetricExporterModel() + new PullMetricExporterModel() .withPrometheus( - new PrometheusModel().withHost("localhost").withPort(9464)))); + new PrometheusModel() + .withHost("localhost") + .withPort(9464) + .withWithoutUnits(false) + .withWithoutTypeSuffix(false) + .withWithoutScopeInfo(false) + .withWithResourceConstantLabels( + new IncludeExcludeModel() + .withIncluded(Collections.singletonList("service*")) + .withExcluded( + Collections.singletonList("service.attr1")))))) + .withProducers( + Collections.singletonList( + new MetricProducerModel().withOpencensus(new OpencensusModel()))); MetricReaderModel metricReader2 = new MetricReaderModel() .withPeriodic( @@ -219,28 +304,36 @@ void parse_KitchenSinkExampleFile() throws IOException { .withInterval(5_000) .withTimeout(30_000) .withExporter( - new MetricExporterModel() + new PushMetricExporterModel() .withOtlp( new OtlpMetricModel() .withProtocol("http/protobuf") - .withEndpoint("http://localhost:4318") + .withEndpoint("http://localhost:4318/v1/metrics") .withCertificate("/app/cert.pem") .withClientKey("/app/cert.pem") .withClientCertificate("/app/cert.pem") .withHeaders( - new HeadersModel() - .withAdditionalProperty("api-key", "1234")) + Collections.singletonList( + new NameStringValuePairModel() + .withName("api-key") + .withValue("1234"))) + .withHeadersList("api-key=1234") .withCompression("gzip") .withTimeout(10_000) + .withInsecure(false) .withTemporalityPreference("delta") .withDefaultHistogramAggregation( OtlpMetricModel.DefaultHistogramAggregation - .BASE_2_EXPONENTIAL_BUCKET_HISTOGRAM)))); + .BASE_2_EXPONENTIAL_BUCKET_HISTOGRAM)))) + .withProducers( + Collections.singletonList( + new MetricProducerModel() + .withAdditionalProperty("prometheus", Collections.emptyMap()))); MetricReaderModel metricReader3 = new MetricReaderModel() .withPeriodic( new PeriodicMetricReaderModel() - .withExporter(new MetricExporterModel().withConsole(new ConsoleModel()))); + .withExporter(new PushMetricExporterModel().withConsole(new ConsoleModel()))); meterProvider.withReaders(Arrays.asList(metricReader1, metricReader2, metricReader3)); ViewModel view = @@ -266,12 +359,91 @@ void parse_KitchenSinkExampleFile() throws IOException { 0.0, 5.0, 10.0, 25.0, 50.0, 75.0, 100.0, 250.0, 500.0, 750.0, 1000.0, 2500.0, 5000.0, 7500.0, 10000.0)) .withRecordMinMax(true))) - .withAttributeKeys(Arrays.asList("key1", "key2"))); + .withAttributeKeys( + new IncludeExcludeModel() + .withIncluded(Arrays.asList("key1", "key2")) + .withExcluded(Collections.singletonList("key3")))); meterProvider.withViews(Collections.singletonList(view)); expected.withMeterProvider(meterProvider); // end MeterProvider config + // start instrumentation config + InstrumentationModel instrumentation = + new InstrumentationModel() + .withGeneral( + new GeneralInstrumentationModel() + .withPeer( + new PeerModel() + .withServiceMapping( + Arrays.asList( + new ServiceMappingModel() + .withPeer("1.2.3.4") + .withService("FooService"), + new ServiceMappingModel() + .withPeer("2.3.4.5") + .withService("BarService")))) + .withHttp( + new HttpModel() + .withClient( + new ClientModel() + .withRequestCapturedHeaders( + Arrays.asList("Content-Type", "Accept")) + .withResponseCapturedHeaders( + Arrays.asList("Content-Type", "Content-Encoding"))) + .withServer( + new ServerModel() + .withRequestCapturedHeaders( + Arrays.asList("Content-Type", "Accept")) + .withResponseCapturedHeaders( + Arrays.asList("Content-Type", "Content-Encoding"))))) + .withCpp( + new LanguageSpecificInstrumentationModel() + .withAdditionalProperty( + "example", Collections.singletonMap("property", "value"))) + .withDotnet( + new LanguageSpecificInstrumentationModel() + .withAdditionalProperty( + "example", Collections.singletonMap("property", "value"))) + .withErlang( + new LanguageSpecificInstrumentationModel() + .withAdditionalProperty( + "example", Collections.singletonMap("property", "value"))) + .withGo( + new LanguageSpecificInstrumentationModel() + .withAdditionalProperty( + "example", Collections.singletonMap("property", "value"))) + .withJava( + new LanguageSpecificInstrumentationModel() + .withAdditionalProperty( + "example", Collections.singletonMap("property", "value"))) + .withJs( + new LanguageSpecificInstrumentationModel() + .withAdditionalProperty( + "example", Collections.singletonMap("property", "value"))) + .withPhp( + new LanguageSpecificInstrumentationModel() + .withAdditionalProperty( + "example", Collections.singletonMap("property", "value"))) + .withPython( + new LanguageSpecificInstrumentationModel() + .withAdditionalProperty( + "example", Collections.singletonMap("property", "value"))) + .withRuby( + new LanguageSpecificInstrumentationModel() + .withAdditionalProperty( + "example", Collections.singletonMap("property", "value"))) + .withRust( + new LanguageSpecificInstrumentationModel() + .withAdditionalProperty( + "example", Collections.singletonMap("property", "value"))) + .withSwift( + new LanguageSpecificInstrumentationModel() + .withAdditionalProperty( + "example", Collections.singletonMap("property", "value"))); + expected.withInstrumentation(instrumentation); + // end instrumentation config + try (FileInputStream configExampleFile = new FileInputStream(System.getenv("CONFIG_EXAMPLE_DIR") + "/kitchen-sink.yaml")) { OpenTelemetryConfigurationModel config = FileConfiguration.parse(configExampleFile); @@ -293,7 +465,7 @@ void parse_KitchenSinkExampleFile() throws IOException { LoggerProviderModel configLoggerProvider = config.getLoggerProvider(); assertThat(configLoggerProvider.getLimits()).isEqualTo(logRecordLimits); assertThat(configLoggerProvider.getProcessors()) - .isEqualTo(Collections.singletonList(logRecordProcessor)); + .isEqualTo(Arrays.asList(logRecordProcessor1, logRecordProcessor2)); // MeterProvider config MeterProviderModel configMeterProvider = config.getMeterProvider(); @@ -301,6 +473,10 @@ void parse_KitchenSinkExampleFile() throws IOException { .isEqualTo(Arrays.asList(metricReader1, metricReader2, metricReader3)); assertThat(configMeterProvider.getViews()).isEqualTo(Collections.singletonList(view)); + // Instrumentation config + InstrumentationModel configInstrumentation = config.getInstrumentation(); + assertThat(configInstrumentation).isEqualTo(instrumentation); + // All configuration assertThat(config).isEqualTo(expected); } @@ -446,6 +622,15 @@ private static java.util.stream.Stream envVarSubstitutionArgs() { Arguments.of( "key1: ${STR_1} value1\n" + "key2: value2\n", mapOf(entry("key1", "value1 value1"), entry("key2", "value2"))), + // Default cases + Arguments.of("key1: ${NOT_SET:-value1}\n", mapOf(entry("key1", "value1"))), + Arguments.of("key1: ${NOT_SET:-true}\n", mapOf(entry("key1", true))), + Arguments.of("key1: ${NOT_SET:-1}\n", mapOf(entry("key1", 1))), + Arguments.of("key1: ${NOT_SET:-1.1}\n", mapOf(entry("key1", 1.1))), + Arguments.of("key1: ${NOT_SET:-0xdeadbeef}\n", mapOf(entry("key1", 3735928559L))), + Arguments.of( + "key1: ${NOT_SET:-value1} value2\n" + "key2: value2\n", + mapOf(entry("key1", "value1 value2"), entry("key2", "value2"))), // Multiple environment variables referenced Arguments.of("key1: ${STR_1}${STR_2}\n", mapOf(entry("key1", "value1value2"))), Arguments.of("key1: ${STR_1} ${STR_2}\n", mapOf(entry("key1", "value1 value2"))), 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 index e826411feee..a9d9e4019c9 100644 --- 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 @@ -13,7 +13,6 @@ import static org.mockito.Mockito.verify; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import com.linecorp.armeria.testing.junit5.server.SelfSignedCertificateExtension; import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter; import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter; @@ -22,8 +21,8 @@ import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; import io.opentelemetry.sdk.extension.incubator.fileconfig.component.LogRecordExporterComponentProvider; -import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.HeadersModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordExporterModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.NameStringValuePairModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OtlpModel; import io.opentelemetry.sdk.logs.export.LogRecordExporter; import java.io.Closeable; @@ -32,6 +31,7 @@ import java.security.cert.CertificateEncodingException; import java.time.Duration; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -127,11 +127,15 @@ void create_OtlpConfigured(@TempDir Path tempDir) .withOtlp( new OtlpModel() .withProtocol("http/protobuf") - .withEndpoint("http://example:4318") + .withEndpoint("http://example:4318/v1/logs") .withHeaders( - new HeadersModel() - .withAdditionalProperty("key1", "value1") - .withAdditionalProperty("key2", "value2")) + Arrays.asList( + new NameStringValuePairModel() + .withName("key1") + .withValue("value1"), + new NameStringValuePairModel() + .withName("key2") + .withValue("value2"))) .withCompression("gzip") .withTimeout(15_000) .withCertificate(certificatePath) @@ -150,12 +154,19 @@ void create_OtlpConfigured(@TempDir Path tempDir) .loadComponent(eq(LogRecordExporter.class), eq("otlp"), configCaptor.capture()); StructuredConfigProperties configProperties = configCaptor.getValue(); assertThat(configProperties.getString("protocol")).isEqualTo("http/protobuf"); - assertThat(configProperties.getString("endpoint")).isEqualTo("http://example:4318"); - StructuredConfigProperties headers = configProperties.getStructured("headers"); - assertThat(headers).isNotNull(); - assertThat(headers.getPropertyKeys()).isEqualTo(ImmutableSet.of("key1", "key2")); - assertThat(headers.getString("key1")).isEqualTo("value1"); - assertThat(headers.getString("key2")).isEqualTo("value2"); + assertThat(configProperties.getString("endpoint")).isEqualTo("http://example:4318/v1/logs"); + List headers = configProperties.getStructuredList("headers"); + assertThat(headers) + .isNotNull() + .satisfiesExactly( + header -> { + assertThat(header.getString("name")).isEqualTo("key1"); + assertThat(header.getString("value")).isEqualTo("value1"); + }, + header -> { + assertThat(header.getString("name")).isEqualTo("key2"); + assertThat(header.getString("value")).isEqualTo("value2"); + }); assertThat(configProperties.getString("compression")).isEqualTo("gzip"); assertThat(configProperties.getInt("timeout")).isEqualTo(Duration.ofSeconds(15).toMillis()); assertThat(configProperties.getString("certificate")).isEqualTo(certificatePath); diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MeterProviderFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MeterProviderFactoryTest.java index 8094bb639c0..c84e0744992 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MeterProviderFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MeterProviderFactoryTest.java @@ -11,10 +11,10 @@ import io.opentelemetry.internal.testing.CleanupExtension; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MeterProviderModel; -import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricExporterModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricReaderModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OtlpMetricModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PeriodicMetricReaderModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PushMetricExporterModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SelectorModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.StreamModel; import io.opentelemetry.sdk.metrics.InstrumentSelector; @@ -75,7 +75,7 @@ void create_Configured() { .withPeriodic( new PeriodicMetricReaderModel() .withExporter( - new MetricExporterModel() + new PushMetricExporterModel() .withOtlp(new OtlpMetricModel()))))) .withViews( Collections.singletonList( diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricExporterFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricExporterFactoryTest.java index 3707a2d582c..4f8b47f2e0c 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricExporterFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricExporterFactoryTest.java @@ -13,7 +13,6 @@ import static org.mockito.Mockito.verify; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import com.linecorp.armeria.testing.junit5.server.SelfSignedCertificateExtension; import io.opentelemetry.exporter.logging.LoggingMetricExporter; import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; @@ -24,9 +23,8 @@ import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; import io.opentelemetry.sdk.extension.incubator.fileconfig.component.MetricExporterComponentProvider; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ConsoleModel; -import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.HeadersModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.NameStringValuePairModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OtlpMetricModel; -import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PrometheusModel; import io.opentelemetry.sdk.metrics.Aggregation; import io.opentelemetry.sdk.metrics.InstrumentType; import io.opentelemetry.sdk.metrics.export.AggregationTemporalitySelector; @@ -38,6 +36,7 @@ import java.security.cert.CertificateEncodingException; import java.time.Duration; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -73,7 +72,7 @@ void create_OtlpDefaults() { MetricExporterFactory.getInstance() .create( new io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model - .MetricExporterModel() + .PushMetricExporterModel() .withOtlp(new OtlpMetricModel()), spiHelper, closeables); @@ -131,15 +130,19 @@ void create_OtlpConfigured(@TempDir Path tempDir) MetricExporterFactory.getInstance() .create( new io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model - .MetricExporterModel() + .PushMetricExporterModel() .withOtlp( new OtlpMetricModel() .withProtocol("http/protobuf") - .withEndpoint("http://example:4318") + .withEndpoint("http://example:4318/v1/metrics") .withHeaders( - new HeadersModel() - .withAdditionalProperty("key1", "value1") - .withAdditionalProperty("key2", "value2")) + Arrays.asList( + new NameStringValuePairModel() + .withName("key1") + .withValue("value1"), + new NameStringValuePairModel() + .withName("key2") + .withValue("value2"))) .withCompression("gzip") .withTimeout(15_000) .withCertificate(certificatePath) @@ -161,12 +164,19 @@ void create_OtlpConfigured(@TempDir Path tempDir) verify(spiHelper).loadComponent(eq(MetricExporter.class), eq("otlp"), configCaptor.capture()); StructuredConfigProperties configProperties = configCaptor.getValue(); assertThat(configProperties.getString("protocol")).isEqualTo("http/protobuf"); - assertThat(configProperties.getString("endpoint")).isEqualTo("http://example:4318"); - StructuredConfigProperties headers = configProperties.getStructured("headers"); - assertThat(headers).isNotNull(); - assertThat(headers.getPropertyKeys()).isEqualTo(ImmutableSet.of("key1", "key2")); - assertThat(headers.getString("key1")).isEqualTo("value1"); - assertThat(headers.getString("key2")).isEqualTo("value2"); + assertThat(configProperties.getString("endpoint")).isEqualTo("http://example:4318/v1/metrics"); + List headers = configProperties.getStructuredList("headers"); + assertThat(headers) + .isNotNull() + .satisfiesExactly( + header -> { + assertThat(header.getString("name")).isEqualTo("key1"); + assertThat(header.getString("value")).isEqualTo("value1"); + }, + header -> { + assertThat(header.getString("name")).isEqualTo("key2"); + assertThat(header.getString("value")).isEqualTo("value2"); + }); assertThat(configProperties.getString("compression")).isEqualTo("gzip"); assertThat(configProperties.getInt("timeout")).isEqualTo(Duration.ofSeconds(15).toMillis()); assertThat(configProperties.getString("certificate")).isEqualTo(certificatePath); @@ -188,7 +198,7 @@ void create_Console() { MetricExporterFactory.getInstance() .create( new io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model - .MetricExporterModel() + .PushMetricExporterModel() .withConsole(new ConsoleModel()), spiHelper, closeables); @@ -198,24 +208,6 @@ void create_Console() { assertThat(exporter.toString()).isEqualTo(expectedExporter.toString()); } - @Test - void create_PrometheusExporter() { - List closeables = new ArrayList<>(); - - assertThatThrownBy( - () -> - MetricExporterFactory.getInstance() - .create( - new io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model - .MetricExporterModel() - .withPrometheus(new PrometheusModel()), - spiHelper, - closeables)) - .isInstanceOf(ConfigurationException.class) - .hasMessage("prometheus exporter not supported in this context"); - cleanup.addCloseables(closeables); - } - @Test void create_SpiExporter_Unknown() { assertThatThrownBy( @@ -223,7 +215,7 @@ void create_SpiExporter_Unknown() { MetricExporterFactory.getInstance() .create( new io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model - .MetricExporterModel() + .PushMetricExporterModel() .withAdditionalProperty( "unknown_key", ImmutableMap.of("key1", "value1")), spiHelper, @@ -239,7 +231,7 @@ void create_SpiExporter_Valid() { MetricExporterFactory.getInstance() .create( new io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model - .MetricExporterModel() + .PushMetricExporterModel() .withAdditionalProperty("test", ImmutableMap.of("key1", "value1")), spiHelper, new ArrayList<>()); diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactoryTest.java index 976bb6da3a2..cde7a9db49b 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactoryTest.java @@ -17,12 +17,13 @@ import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; -import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricExporterModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricReaderModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OtlpMetricModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PeriodicMetricReaderModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PrometheusModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PullMetricExporterModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PullMetricReaderModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PushMetricExporterModel; import java.io.Closeable; import java.io.IOException; import java.net.ServerSocket; @@ -72,7 +73,7 @@ void create_PeriodicDefaults() { .withPeriodic( new PeriodicMetricReaderModel() .withExporter( - new MetricExporterModel().withOtlp(new OtlpMetricModel()))), + new PushMetricExporterModel().withOtlp(new OtlpMetricModel()))), spiHelper, closeables); cleanup.addCloseable(reader); @@ -97,7 +98,8 @@ void create_PeriodicConfigured() { new MetricReaderModel() .withPeriodic( new PeriodicMetricReaderModel() - .withExporter(new MetricExporterModel().withOtlp(new OtlpMetricModel())) + .withExporter( + new PushMetricExporterModel().withOtlp(new OtlpMetricModel())) .withInterval(1)), spiHelper, closeables); @@ -123,7 +125,7 @@ void create_PullPrometheusDefault() throws IOException { .withPull( new PullMetricReaderModel() .withExporter( - new MetricExporterModel() + new PullMetricExporterModel() .withPrometheus(new PrometheusModel().withPort(port)))), spiHelper, closeables); @@ -153,7 +155,7 @@ void create_PullPrometheusConfigured() throws IOException { .withPull( new PullMetricReaderModel() .withExporter( - new MetricExporterModel() + new PullMetricExporterModel() .withPrometheus( new PrometheusModel() .withHost("localhost") @@ -187,21 +189,7 @@ void create_InvalidPullReader() { new MetricReaderModel() .withPull( new PullMetricReaderModel() - .withExporter(new MetricExporterModel())), - spiHelper, - Collections.emptyList())) - .isInstanceOf(ConfigurationException.class) - .hasMessage("prometheus is the only currently supported pull reader"); - - assertThatThrownBy( - () -> - MetricReaderFactory.getInstance() - .create( - new MetricReaderModel() - .withPull( - new PullMetricReaderModel() - .withExporter( - new MetricExporterModel().withOtlp(new OtlpMetricModel()))), + .withExporter(new PullMetricExporterModel())), spiHelper, Collections.emptyList())) .isInstanceOf(ConfigurationException.class) 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 index 247e5932a23..0284a16bac4 100644 --- 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 @@ -24,7 +24,7 @@ import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AlwaysOnModel; -import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AttributesModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AttributeNameValueModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchLogRecordProcessorModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchSpanProcessorModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordExporterModel; @@ -32,13 +32,13 @@ import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordProcessorModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LoggerProviderModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MeterProviderModel; -import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricExporterModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricReaderModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OtlpMetricModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OtlpModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PeriodicMetricReaderModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PropagatorModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PushMetricExporterModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ResourceModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SamplerModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SelectorModel; @@ -222,9 +222,13 @@ void create_Configured() { .withResource( new ResourceModel() .withAttributes( - new AttributesModel() - .withServiceName("my-service") - .withAdditionalProperty("key", "val"))) + Arrays.asList( + new AttributeNameValueModel() + .withName("service.name") + .withValue("my-service"), + new AttributeNameValueModel() + .withName("key") + .withValue("val")))) .withLoggerProvider( new LoggerProviderModel() .withLimits( @@ -267,7 +271,7 @@ void create_Configured() { .withPeriodic( new PeriodicMetricReaderModel() .withExporter( - new MetricExporterModel() + new PushMetricExporterModel() .withOtlp(new OtlpMetricModel()))))) .withViews( Collections.singletonList( diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ResourceFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ResourceFactoryTest.java index d567d7f45e4..e863a4ecb51 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ResourceFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ResourceFactoryTest.java @@ -9,11 +9,20 @@ import static org.mockito.Mockito.spy; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; -import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AttributesModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AttributeNameValueModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.DetectorAttributesModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.DetectorsModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ResourceModel; import io.opentelemetry.sdk.resources.Resource; +import java.util.Arrays; import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; +import javax.annotation.Nullable; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; class ResourceFactoryTest { @@ -27,11 +36,14 @@ void create() { .create( new ResourceModel() .withAttributes( - new AttributesModel() - .withServiceName("my-service") - .withAdditionalProperty("key", "val") - // Should override shape attribute from ResourceComponentProvider - .withAdditionalProperty("shape", "circle")), + Arrays.asList( + new AttributeNameValueModel() + .withName("service.name") + .withValue("my-service"), + new AttributeNameValueModel().withName("key").withValue("val"), + new AttributeNameValueModel() + .withName("shape") + .withValue("circle"))), spiHelper, Collections.emptyList())) .isEqualTo( @@ -46,4 +58,86 @@ void create() { .put("order", "second") .build()); } + + @ParameterizedTest + @MethodSource("createWithDetectorsArgs") + void createWithDetectors( + @Nullable List included, @Nullable List excluded, Resource expectedResource) { + ResourceModel resourceModel = + new ResourceModel() + .withDetectors( + new DetectorsModel() + .withAttributes( + new DetectorAttributesModel() + .withIncluded(included) + .withExcluded(excluded))); + Resource resource = + ResourceFactory.getInstance().create(resourceModel, spiHelper, Collections.emptyList()); + assertThat(resource).isEqualTo(expectedResource); + } + + private static Stream createWithDetectorsArgs() { + return Stream.of( + Arguments.of( + null, + null, + Resource.getDefault().toBuilder() + .put("color", "red") + .put("shape", "square") + .put("order", "second") + .build()), + Arguments.of( + Collections.singletonList("color"), + null, + Resource.getDefault().toBuilder().put("color", "red").build()), + Arguments.of( + Arrays.asList("color", "shape"), + null, + Resource.getDefault().toBuilder().put("color", "red").put("shape", "square").build()), + Arguments.of( + null, + Collections.singletonList("color"), + Resource.getDefault().toBuilder() + .put("shape", "square") + .put("order", "second") + .build()), + Arguments.of( + null, + Arrays.asList("color", "shape"), + Resource.getDefault().toBuilder().put("order", "second").build()), + Arguments.of( + Collections.singletonList("color"), + Collections.singletonList("color"), + Resource.getDefault().toBuilder().build()), + Arguments.of( + Arrays.asList("color", "shape"), + Collections.singletonList("color"), + Resource.getDefault().toBuilder().put("shape", "square").build()), + Arguments.of( + Collections.singletonList("c*"), + null, + Resource.getDefault().toBuilder().put("color", "red").build()), + Arguments.of( + Collections.singletonList("c?lor"), + null, + Resource.getDefault().toBuilder().put("color", "red").build()), + Arguments.of( + null, + Collections.singletonList("c*"), + Resource.getDefault().toBuilder() + .put("shape", "square") + .put("order", "second") + .build()), + Arguments.of( + null, + Collections.singletonList("c?lor"), + Resource.getDefault().toBuilder() + .put("shape", "square") + .put("order", "second") + .build()), + Arguments.of( + Collections.singletonList("*o*"), + Collections.singletonList("order"), + Resource.getDefault().toBuilder().put("color", "red").build())); + } } diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanExporterFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanExporterFactoryTest.java index 942c9f4352e..a6edf4039f1 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanExporterFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanExporterFactoryTest.java @@ -13,7 +13,6 @@ import static org.mockito.Mockito.verify; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import com.linecorp.armeria.testing.junit5.server.SelfSignedCertificateExtension; import io.opentelemetry.exporter.logging.LoggingSpanExporter; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; @@ -25,7 +24,7 @@ import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; import io.opentelemetry.sdk.extension.incubator.fileconfig.component.SpanExporterComponentProvider; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ConsoleModel; -import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.HeadersModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.NameStringValuePairModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OtlpModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ZipkinModel; import io.opentelemetry.sdk.trace.export.SpanExporter; @@ -35,6 +34,7 @@ import java.security.cert.CertificateEncodingException; import java.time.Duration; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -126,11 +126,15 @@ void create_OtlpConfigured(@TempDir Path tempDir) .withOtlp( new OtlpModel() .withProtocol("http/protobuf") - .withEndpoint("http://example:4318") + .withEndpoint("http://example:4318/v1/traces") .withHeaders( - new HeadersModel() - .withAdditionalProperty("key1", "value1") - .withAdditionalProperty("key2", "value2")) + Arrays.asList( + new NameStringValuePairModel() + .withName("key1") + .withValue("value1"), + new NameStringValuePairModel() + .withName("key2") + .withValue("value2"))) .withCompression("gzip") .withTimeout(15_000) .withCertificate(certificatePath) @@ -148,12 +152,19 @@ void create_OtlpConfigured(@TempDir Path tempDir) verify(spiHelper).loadComponent(eq(SpanExporter.class), eq("otlp"), configCaptor.capture()); StructuredConfigProperties configProperties = configCaptor.getValue(); assertThat(configProperties.getString("protocol")).isEqualTo("http/protobuf"); - assertThat(configProperties.getString("endpoint")).isEqualTo("http://example:4318"); - StructuredConfigProperties headers = configProperties.getStructured("headers"); - assertThat(headers).isNotNull(); - assertThat(headers.getPropertyKeys()).isEqualTo(ImmutableSet.of("key1", "key2")); - assertThat(headers.getString("key1")).isEqualTo("value1"); - assertThat(headers.getString("key2")).isEqualTo("value2"); + assertThat(configProperties.getString("endpoint")).isEqualTo("http://example:4318/v1/traces"); + List headers = configProperties.getStructuredList("headers"); + assertThat(headers) + .isNotNull() + .satisfiesExactly( + header -> { + assertThat(header.getString("name")).isEqualTo("key1"); + assertThat(header.getString("value")).isEqualTo("value1"); + }, + header -> { + assertThat(header.getString("name")).isEqualTo("key2"); + assertThat(header.getString("value")).isEqualTo("value2"); + }); assertThat(configProperties.getString("compression")).isEqualTo("gzip"); assertThat(configProperties.getInt("timeout")).isEqualTo(Duration.ofSeconds(15).toMillis()); assertThat(configProperties.getString("certificate")).isEqualTo(certificatePath); diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ViewFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ViewFactoryTest.java index 3e09caa4ad9..9196b354bf3 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ViewFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ViewFactoryTest.java @@ -11,6 +11,7 @@ import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AggregationModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExplicitBucketHistogramModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.IncludeExcludeModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.StreamModel; import io.opentelemetry.sdk.metrics.View; import java.util.Arrays; @@ -52,7 +53,8 @@ void create() { new StreamModel() .withName("name") .withDescription("description") - .withAttributeKeys(Arrays.asList("foo", "bar")) + .withAttributeKeys( + new IncludeExcludeModel().withIncluded(Arrays.asList("foo", "bar"))) .withAggregation( new AggregationModel() .withExplicitBucketHistogram( diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/YamlStructuredConfigPropertiesTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/YamlStructuredConfigPropertiesTest.java index 7e6ba27b522..2e64e93fe94 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/YamlStructuredConfigPropertiesTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/YamlStructuredConfigPropertiesTest.java @@ -26,7 +26,8 @@ class YamlStructuredConfigPropertiesTest { + "\n" + "resource:\n" + " attributes:\n" - + " service.name: \"unknown_service\"\n" + + " - name: service.name\n" + + " value: \"unknown_service\"\n" + "\n" + "other:\n" + " str_key: str_value\n" @@ -70,9 +71,15 @@ void configurationSchema() { assertThat(structuredConfigProps.getString("file_format")).isEqualTo("0.1"); StructuredConfigProperties resourceProps = structuredConfigProps.getStructured("resource"); assertThat(resourceProps).isNotNull(); - StructuredConfigProperties resourceAttributesProps = resourceProps.getStructured("attributes"); - assertThat(resourceAttributesProps).isNotNull(); - assertThat(resourceAttributesProps.getString("service.name")).isEqualTo("unknown_service"); + List resourceAttributesList = + resourceProps.getStructuredList("attributes"); + assertThat(resourceAttributesList) + .isNotNull() + .satisfiesExactly( + attributeEntry -> { + assertThat(attributeEntry.getString("name")).isEqualTo("service.name"); + assertThat(attributeEntry.getString("value")).isEqualTo("unknown_service"); + }); } @Test