Before parsing, environment variable substitution is performed as described in {@link
- * EnvSubstitutionConstructor}.
+ *
During parsing, environment variable substitution is performed as defined in the
+ * OpenTelemetry Configuration Data Model specification.
*
* @throws DeclarativeConfigException if unable to parse
*/
@@ -149,7 +152,7 @@ static OpenTelemetryConfigurationModel parse(
// Visible for testing
static Object loadYaml(InputStream inputStream, Map environmentVariables) {
LoadSettings settings = LoadSettings.builder().setSchema(new CoreSchema()).build();
- Load yaml = new Load(settings, new EnvSubstitutionConstructor(settings, environmentVariables));
+ Load yaml = new EnvLoad(settings, environmentVariables);
return yaml.loadFromInputStream(inputStream);
}
@@ -241,89 +244,110 @@ static R createAndMaybeCleanup(Factory factory, SpiHelper spiHelper
}
}
+ private static final class EnvLoad extends Load {
+
+ private final LoadSettings settings;
+ private final Map environmentVariables;
+
+ public EnvLoad(LoadSettings settings, Map environmentVariables) {
+ super(settings);
+ this.settings = settings;
+ this.environmentVariables = environmentVariables;
+ }
+
+ @Override
+ public Object loadFromInputStream(InputStream yamlStream) {
+ Objects.requireNonNull(yamlStream, "InputStream cannot be null");
+ return loadOne(
+ new EnvComposer(
+ settings,
+ new ParserImpl(
+ settings, new StreamReader(settings, new YamlUnicodeReader(yamlStream))),
+ environmentVariables));
+ }
+ }
+
/**
- * {@link StandardConstructor} which substitutes environment variables.
+ * A YAML Composer that performs environment variable substitution according to the
+ * OpenTelemetry Configuration Data Model specification.
+ *
+ *
This composer supports:
*
- *
Environment variables follow the syntax {@code ${VARIABLE}}, where {@code VARIABLE} is an
- * environment variable matching the regular expression {@code [a-zA-Z_]+[a-zA-Z0-9_]*}.
+ *
+ *
Environment variable references: {@code ${ENV_VAR}} or {@code ${env:ENV_VAR}}
+ *
Escape sequences: {@code $$} is replaced with a single {@code $}
+ *
*
- *
Environment variable substitution only takes place on scalar values of maps. References to
- * environment variables in keys or sets are ignored.
+ *
Environment variable substitution only applies to scalar values. Mapping keys are not
+ * candidates for substitution. Referenced environment variables that are undefined, null, or
+ * empty are replaced with empty values unless a default value is provided.
*
- *
If a referenced environment variable is not defined, it is replaced with {@code ""}.
+ *
The {@code $} character serves as an escape sequence where {@code $$} in the input is
+ * translated to a single {@code $} in the output. This prevents environment variable substitution
+ * for the escaped content.
*/
- private static final class EnvSubstitutionConstructor extends StandardConstructor {
+ private static final class EnvComposer extends Composer {
- // Load is not thread safe but this instance is always used on the same thread
private final Load load;
private final Map environmentVariables;
+ private final ScalarResolver scalarResolver;
- private EnvSubstitutionConstructor(
- LoadSettings loadSettings, Map environmentVariables) {
- super(loadSettings);
- load = new Load(loadSettings);
+ private static final String ESCAPE_SEQUENCE = "$$";
+ private static final int ESCAPE_SEQUENCE_LENGTH = ESCAPE_SEQUENCE.length();
+ private static final char ESCAPE_SEQUENCE_REPLACEMENT = '$';
+
+ public EnvComposer(
+ LoadSettings settings, ParserImpl parser, Map environmentVariables) {
+ super(settings, parser);
+ this.load = new Load(settings);
this.environmentVariables = environmentVariables;
+ this.scalarResolver = settings.getSchema().getScalarResolver();
}
- /**
- * Implementation is same as {@link
- * org.snakeyaml.engine.v2.constructor.BaseConstructor#constructMapping(MappingNode)} except we
- * override the resolution of values with our custom {@link #constructValueObject(Node)}, which
- * performs environment variable substitution.
- */
@Override
- @SuppressWarnings({"ReturnValueIgnored", "CatchingUnchecked"})
- protected Map