-
Notifications
You must be signed in to change notification settings - Fork 940
fix: Add system property substitution to declarative config #8073
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
MikeGoldsmith
wants to merge
3
commits into
open-telemetry:main
Choose a base branch
from
honeycombio:mike/add-system-property-substitution
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+200
−21
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -57,8 +57,9 @@ | |
| public final class DeclarativeConfiguration { | ||
|
|
||
| private static final Logger logger = Logger.getLogger(DeclarativeConfiguration.class.getName()); | ||
| // Matches ${VAR_NAME}, ${env:VAR_NAME}, or ${sys:property.name} with optional :-default | ||
| private static final Pattern ENV_VARIABLE_REFERENCE = | ||
| Pattern.compile("\\$\\{([a-zA-Z_][a-zA-Z0-9_]*)(:-([^\n}]*))?}"); | ||
| Pattern.compile("\\$\\{(?:(env|sys):)?([a-zA-Z_][a-zA-Z0-9_.]*)(?::-([^\\n}]*))?}"); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Even if we don't add support for the |
||
| private static final ComponentLoader DEFAULT_COMPONENT_LOADER = | ||
| ComponentLoader.forClassLoader(DeclarativeConfigProperties.class.getClassLoader()); | ||
|
|
||
|
|
@@ -140,31 +141,37 @@ private static ExtendedOpenTelemetrySdk create( | |
| /** | ||
| * Parse the {@code configuration} YAML and return the {@link OpenTelemetryConfigurationModel}. | ||
| * | ||
| * <p>During parsing, environment variable substitution is performed as defined in the <a | ||
| * <p>During parsing, environment variable and system property substitution is performed as | ||
| * defined in the <a | ||
| * href="https://opentelemetry.io/docs/specs/otel/configuration/data-model/#environment-variable-substitution"> | ||
| * OpenTelemetry Configuration Data Model specification</a>. | ||
| * | ||
| * @throws DeclarativeConfigException if unable to parse | ||
| */ | ||
| public static OpenTelemetryConfigurationModel parse(InputStream configuration) { | ||
| try { | ||
| return parse(configuration, System.getenv()); | ||
| return parse(configuration, System.getenv(), System.getProperties()); | ||
| } catch (RuntimeException e) { | ||
| throw new DeclarativeConfigException("Unable to parse configuration input stream", e); | ||
| } | ||
| } | ||
|
|
||
| // Visible for testing | ||
| static OpenTelemetryConfigurationModel parse( | ||
| InputStream configuration, Map<String, String> environmentVariables) { | ||
| Object yamlObj = loadYaml(configuration, environmentVariables); | ||
| InputStream configuration, | ||
| Map<String, String> environmentVariables, | ||
| Map<Object, Object> systemProperties) { | ||
| Object yamlObj = loadYaml(configuration, environmentVariables, systemProperties); | ||
| return MAPPER.convertValue(yamlObj, OpenTelemetryConfigurationModel.class); | ||
| } | ||
|
|
||
| // Visible for testing | ||
| static Object loadYaml(InputStream inputStream, Map<String, String> environmentVariables) { | ||
| static Object loadYaml( | ||
| InputStream inputStream, | ||
| Map<String, String> environmentVariables, | ||
| Map<Object, Object> systemProperties) { | ||
| LoadSettings settings = LoadSettings.builder().setSchema(new CoreSchema()).build(); | ||
| Load yaml = new EnvLoad(settings, environmentVariables); | ||
| Load yaml = new EnvLoad(settings, environmentVariables, systemProperties); | ||
| return yaml.loadFromInputStream(inputStream); | ||
| } | ||
|
|
||
|
|
@@ -185,7 +192,7 @@ public static DeclarativeConfigProperties toConfigProperties(Object model) { | |
| * @return a generic {@link DeclarativeConfigProperties} representation of the model | ||
| */ | ||
| public static DeclarativeConfigProperties toConfigProperties(InputStream configuration) { | ||
| Object yamlObj = loadYaml(configuration, System.getenv()); | ||
| Object yamlObj = loadYaml(configuration, System.getenv(), System.getProperties()); | ||
| return toConfigProperties(yamlObj, DEFAULT_COMPONENT_LOADER); | ||
| } | ||
|
|
||
|
|
@@ -256,11 +263,16 @@ private static final class EnvLoad extends Load { | |
|
|
||
| private final LoadSettings settings; | ||
| private final Map<String, String> environmentVariables; | ||
| private final Map<Object, Object> systemProperties; | ||
|
|
||
| private EnvLoad(LoadSettings settings, Map<String, String> environmentVariables) { | ||
| private EnvLoad( | ||
| LoadSettings settings, | ||
| Map<String, String> environmentVariables, | ||
| Map<Object, Object> systemProperties) { | ||
| super(settings); | ||
| this.settings = settings; | ||
| this.environmentVariables = environmentVariables; | ||
| this.systemProperties = systemProperties; | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -271,46 +283,54 @@ public Object loadFromInputStream(InputStream yamlStream) { | |
| settings, | ||
| new ParserImpl( | ||
| settings, new StreamReader(settings, new YamlUnicodeReader(yamlStream))), | ||
| environmentVariables)); | ||
| environmentVariables, | ||
| systemProperties)); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * A YAML Composer that performs environment variable substitution according to the <a | ||
| * A YAML Composer that performs environment variable and system property substitution according | ||
| * to the <a | ||
| * href="https://opentelemetry.io/docs/specs/otel/configuration/data-model/#environment-variable-substitution"> | ||
| * OpenTelemetry Configuration Data Model specification</a>. | ||
| * | ||
| * <p>This composer supports: | ||
| * | ||
| * <ul> | ||
| * <li>Environment variable references: {@code ${ENV_VAR}} or {@code ${env:ENV_VAR}} | ||
| * <li>System property references: {@code ${sys:property.name}} | ||
| * <li>Default values: {@code ${ENV_VAR:-default_value}} | ||
| * <li>Escape sequences: {@code $$} is replaced with a single {@code $} | ||
| * </ul> | ||
| * | ||
| * <p>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. | ||
| * <p>Substitution only applies to scalar values. Mapping keys are not candidates for | ||
| * substitution. Referenced variables that are undefined, null, or empty are replaced with empty | ||
| * values unless a default value is provided. | ||
| * | ||
| * <p>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. | ||
| * translated to a single {@code $} in the output. This prevents substitution for the escaped | ||
| * content. | ||
| */ | ||
| private static final class EnvComposer extends Composer { | ||
|
|
||
| private final Load load; | ||
| private final Map<String, String> environmentVariables; | ||
| private final Map<Object, Object> systemProperties; | ||
| private final ScalarResolver scalarResolver; | ||
|
|
||
| private static final String ESCAPE_SEQUENCE = "$$"; | ||
| private static final int ESCAPE_SEQUENCE_LENGTH = ESCAPE_SEQUENCE.length(); | ||
| private static final char ESCAPE_SEQUENCE_REPLACEMENT = '$'; | ||
|
|
||
| private EnvComposer( | ||
| LoadSettings settings, ParserImpl parser, Map<String, String> environmentVariables) { | ||
| LoadSettings settings, | ||
| ParserImpl parser, | ||
| Map<String, String> environmentVariables, | ||
| Map<Object, Object> systemProperties) { | ||
| super(settings, parser); | ||
| this.load = new Load(settings); | ||
| this.environmentVariables = environmentVariables; | ||
| this.systemProperties = systemProperties; | ||
| this.scalarResolver = settings.getSchema().getScalarResolver(); | ||
| } | ||
|
|
||
|
|
@@ -397,12 +417,23 @@ private StringBuilder envVarSubstitution( | |
| int offset = 0; | ||
| do { | ||
| MatchResult matchResult = matcher.toMatchResult(); | ||
| String envVarKey = matcher.group(1); | ||
| String prefix = matcher.group(1); // "env", "sys", or null | ||
| String key = matcher.group(2); // variable/property name | ||
| String defaultValue = matcher.group(3); | ||
| if (defaultValue == null) { | ||
| defaultValue = ""; | ||
| } | ||
| String replacement = environmentVariables.getOrDefault(envVarKey, defaultValue); | ||
|
|
||
| String replacement; | ||
| if ("sys".equals(prefix)) { | ||
| // System property substitution | ||
| Object sysProp = systemProperties.get(key); | ||
| replacement = sysProp != null ? sysProp.toString() : defaultValue; | ||
| } else { | ||
| // Environment variable substitution (default or explicit "env:" prefix) | ||
| replacement = environmentVariables.getOrDefault(key, defaultValue); | ||
| } | ||
|
|
||
| newVal.append(val, offset, matchResult.start()).append(replacement); | ||
| offset = matchResult.end(); | ||
| } while (matcher.find()); | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically this is non compliant with the spec. The syntax describes "Optionally followed by env:" with no affordance for alternative prefixes.
I would have preferred none of this prefix business at all, but
env:allows for compatibility with the collector's syntax, which had a lot of momentum and was stabilizing.The collector also supports alternative prefixes to substitute from other places besides env vars (things like other files, network locations), AND allows you to do things like substitute entire objects or array (where as declarative config explicitly limits substitution to primitive types). So collector substitution is a superset of declarative config substitution. The motivation behind this was simplicity: since declarative config is going to be implemented 10+ times, we want the implementation burden to be lower.
The question we need to answer in the java is whether system property support is important enough for us to either:
Personally, I don't think spec non-compliance is that big of a deal here. But I also want to be use case motivated. @MikeGoldsmith (or anyone else) have you heard this requested from users?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, I think it's good to support both env vars and system properties in the Java ecosystem
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've not heard anything directly that using system properties like this is a hard requirement but I definitely see the benefit for those who prefer them over env vars.
I've opened a spec issue and draft PR to add support for per-language prefixes.