-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add InstrumentationDefaults helper to declarative-config-bridge #17816
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
zeitlinger
wants to merge
16
commits into
open-telemetry:main
Choose a base branch
from
zeitlinger:instrumentation-defaults
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.
Open
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
4559fdf
Add InstrumentationDefaults helper to declarative-config-bridge
zeitlinger 0572994
fix: add @CanIgnoreReturnValue to InstrumentationDefaults.setDefault
zeitlinger deef7d8
fix: add @CanIgnoreReturnValue to applyToModel
zeitlinger 97ab9a4
docs: add InstrumentationDefaults usage to declarative-config-bridge …
zeitlinger 1e43af3
docs(declarative-config-bridge): restructure InstrumentationDefaults …
zeitlinger 425d9c2
refactor(declarative-config-bridge): nest InstrumentationDefaults API
zeitlinger ea1616d
docs(declarative-config-bridge): use get instead of getStructured in …
zeitlinger a370c6a
Merge branch 'main' into instrumentation-defaults
trask a7d2e54
fix: mirror development key translation in InstrumentationDefaults
zeitlinger 0f27445
test: use parameterized InstrumentationDefaults translation cases
zeitlinger 5b0f8fc
test: add bridge roundtrip coverage for InstrumentationDefaults
zeitlinger fdadd88
Use HashMap for InstrumentationDefaults storage; iteration order is n…
zeitlinger ef6c451
fix: isolate declarative model support in defaults helper
zeitlinger 93b37ee
fix: export incubator model dependency from config bridge
zeitlinger b4a4100
Merge remote-tracking branch 'origin/main' into lane-pr17816-fix
zeitlinger 256f42b
fix: align config bridge defaults helper with declarative config API
zeitlinger 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
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
172 changes: 172 additions & 0 deletions
172
...ain/java/io/opentelemetry/instrumentation/config/bridge/DefaultInstrumentationConfig.java
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 |
|---|---|---|
| @@ -0,0 +1,172 @@ | ||
| /* | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package io.opentelemetry.instrumentation.config.bridge; | ||
|
|
||
| import static java.util.Collections.emptyList; | ||
|
|
||
| import com.google.errorprone.annotations.CanIgnoreReturnValue; | ||
| import java.util.ArrayList; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| /** | ||
| * Defines instrumentation defaults that work with both traditional property-based configuration and | ||
| * declarative configuration. | ||
| * | ||
| * <p>Navigation mirrors {@link io.opentelemetry.api.incubator.config.DeclarativeConfigProperties}: | ||
| * read-side uses {@code config.get(name).getString(key)}; write-side uses {@code | ||
| * defaults.get(name).setDefault(key, value)}. | ||
| * | ||
| * <p>Usage: | ||
| * | ||
| * <pre>{@code | ||
| * DefaultInstrumentationConfig defaults = new DefaultInstrumentationConfig(); | ||
| * defaults.get("micrometer").setDefault("base_time_unit", "s"); | ||
| * defaults.get("log4j_appender").setDefault("experimental_log_attributes/development", "true"); | ||
| * defaults.addMapping("acme", "acme.full_name"); | ||
| * defaults.get("acme").get("full_name").setDefault("preserved", "true"); | ||
| * | ||
| * autoConfiguration.addPropertiesSupplier(defaults::toConfigProperties); | ||
| * }</pre> | ||
| * | ||
| * <p>For declarative-config model integration, use the optional {@link | ||
| * DefaultInstrumentationConfigApplier} helper so the base defaults type stays usable without the | ||
| * incubator model classes on the runtime classpath. | ||
| */ | ||
| public final class DefaultInstrumentationConfig { | ||
|
|
||
| private final Map<String, String> defaults; | ||
| private final List<String> path; | ||
| private final Map<String, String> propertyMappings; | ||
|
|
||
| public DefaultInstrumentationConfig() { | ||
| this(new HashMap<>(), emptyList(), new HashMap<>()); | ||
| } | ||
|
|
||
| private DefaultInstrumentationConfig( | ||
| Map<String, String> defaults, List<String> path, Map<String, String> propertyMappings) { | ||
| this.defaults = defaults; | ||
| this.path = path; | ||
| this.propertyMappings = propertyMappings; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the defaults node for the given child, mirroring {@code | ||
| * DeclarativeConfigProperties.get(name)} on the read side. | ||
| */ | ||
| public DefaultInstrumentationConfig get(String name) { | ||
| List<String> newPath = new ArrayList<>(path); | ||
| newPath.add(name); | ||
| return new DefaultInstrumentationConfig(defaults, newPath, propertyMappings); | ||
| } | ||
|
|
||
| /** | ||
| * Adds a property prefix mapping, mirroring {@link DeclarativeConfigPropertiesBridgeBuilder | ||
| * #addMapping(String, String)} in the opposite direction. | ||
| * | ||
| * <p>For example, mapping {@code acme} to {@code acme.full_name} makes {@code | ||
| * defaults.get("acme").get("full_name").setDefault("preserved", "true")} produce {@code | ||
| * acme.preserved=true}. | ||
| */ | ||
| @CanIgnoreReturnValue | ||
| public DefaultInstrumentationConfig addMapping(String propertyPrefix, String declarativePath) { | ||
| propertyMappings.put(propertyPrefix, declarativePath); | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Sets a default value for a property on the current node. Keys use the declarative config shape | ||
| * (e.g. {@code base_time_unit}); when producing config property keys, underscores are translated | ||
| * to hyphens and keys ending in {@code /development} follow the same {@code experimental.} | ||
| * translation as {@link ConfigPropertiesBackedDeclarativeConfigProperties}. | ||
| * | ||
| * @return {@code this} for chaining | ||
| */ | ||
| @CanIgnoreReturnValue | ||
| public DefaultInstrumentationConfig setDefault(String key, String value) { | ||
| defaults.put(pathWithName(key), value); | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Translates defaults to config properties for auto-configuration. | ||
| * | ||
| * <p>Defaults use {@code otel.instrumentation.*} keys unless a custom mapping overrides the | ||
| * property prefix for a declarative path subtree. | ||
| */ | ||
| public Map<String, String> toConfigProperties() { | ||
| HashMap<String, String> map = new HashMap<>(); | ||
| defaults.forEach((declarativePath, value) -> map.put(toConfigProperty(declarativePath), value)); | ||
| return map; | ||
| } | ||
|
|
||
| Map<String, String> getDefaults() { | ||
| return defaults; | ||
| } | ||
|
|
||
| private String pathWithName(String name) { | ||
| if (path.isEmpty()) { | ||
| return name; | ||
| } | ||
| return String.join(".", path) + "." + name; | ||
| } | ||
|
|
||
| private String toConfigProperty(String declarativePath) { | ||
| String propertyPrefix = null; | ||
| String declarativePrefix = null; | ||
| for (Map.Entry<String, String> entry : propertyMappings.entrySet()) { | ||
| String candidate = entry.getValue(); | ||
| if (!matchesPrefix(declarativePath, candidate)) { | ||
| continue; | ||
| } | ||
| if (declarativePrefix == null || candidate.length() > declarativePrefix.length()) { | ||
| declarativePrefix = candidate; | ||
| propertyPrefix = entry.getKey(); | ||
| } | ||
| } | ||
|
|
||
| if (propertyPrefix == null) { | ||
| return "otel.instrumentation." + translatePath(declarativePath); | ||
| } | ||
| if (declarativePrefix == null) { | ||
| throw new IllegalStateException("missing declarative prefix for property mapping"); | ||
| } | ||
|
|
||
| if (declarativePath.equals(declarativePrefix)) { | ||
| return propertyPrefix; | ||
| } | ||
|
|
||
| int matchedPrefixLength = declarativePrefix.length(); | ||
| return propertyPrefix + "." + translatePath(declarativePath.substring(matchedPrefixLength + 1)); | ||
| } | ||
|
|
||
| private static boolean matchesPrefix(String path, String prefix) { | ||
| return path.equals(prefix) || path.startsWith(prefix + "."); | ||
| } | ||
|
|
||
| private static String translatePath(String path) { | ||
| String[] segments = path.split("\\."); | ||
| StringBuilder translated = new StringBuilder(); | ||
| for (int i = 0; i < segments.length; i++) { | ||
| if (i > 0) { | ||
| translated.append("."); | ||
| } | ||
| translated.append(translateName(segments[i])); | ||
| } | ||
| return translated.toString(); | ||
| } | ||
|
|
||
| private static String translateName(String name) { | ||
| if (name.endsWith("/development")) { | ||
| name = name.substring(0, name.length() - "/development".length()); | ||
| if (!name.contains("experimental")) { | ||
| name = "experimental." + name; | ||
| } | ||
| } | ||
| return name.replace('_', '-'); | ||
| } | ||
| } |
81 changes: 81 additions & 0 deletions
81
...a/io/opentelemetry/instrumentation/config/bridge/DefaultInstrumentationConfigApplier.java
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 |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| /* | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package io.opentelemetry.instrumentation.config.bridge; | ||
|
|
||
| import com.google.errorprone.annotations.CanIgnoreReturnValue; | ||
| import io.opentelemetry.sdk.declarativeconfig.internal.model.ExperimentalInstrumentationModel; | ||
| import io.opentelemetry.sdk.declarativeconfig.internal.model.ExperimentalLanguageSpecificInstrumentationModel; | ||
| import io.opentelemetry.sdk.declarativeconfig.internal.model.ExperimentalLanguageSpecificInstrumentationPropertyModel; | ||
| import io.opentelemetry.sdk.declarativeconfig.internal.model.OpenTelemetryConfigurationModel; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
|
|
||
| /** Utility that applies {@link DefaultInstrumentationConfig} defaults to the declarative model. */ | ||
| public final class DefaultInstrumentationConfigApplier { | ||
|
|
||
| private DefaultInstrumentationConfigApplier() {} | ||
|
|
||
| /** | ||
| * Applies defaults to the declarative configuration model under {@code | ||
| * instrumentation/development.java}. Existing values in the model take precedence; defaults are | ||
| * only set for properties not already present. | ||
| */ | ||
| @CanIgnoreReturnValue | ||
| public static OpenTelemetryConfigurationModel applyToModel( | ||
| DefaultInstrumentationConfig defaults, OpenTelemetryConfigurationModel model) { | ||
| if (defaults.getDefaults().isEmpty()) { | ||
| return model; | ||
| } | ||
|
|
||
| ExperimentalInstrumentationModel instrumentation = model.getInstrumentationDevelopment(); | ||
| if (instrumentation == null) { | ||
| instrumentation = new ExperimentalInstrumentationModel(); | ||
| model.withInstrumentationDevelopment(instrumentation); | ||
| } | ||
| ExperimentalLanguageSpecificInstrumentationModel java = instrumentation.getJava(); | ||
| if (java == null) { | ||
| java = new ExperimentalLanguageSpecificInstrumentationModel(); | ||
| instrumentation.withJava(java); | ||
| } | ||
|
|
||
| Map<String, ExperimentalLanguageSpecificInstrumentationPropertyModel> props = | ||
| java.getAdditionalProperties(); | ||
|
|
||
| for (Map.Entry<String, String> entry : defaults.getDefaults().entrySet()) { | ||
| applyDefault(props, entry.getKey(), entry.getValue()); | ||
| } | ||
|
|
||
| return model; | ||
| } | ||
|
|
||
| private static void applyDefault( | ||
| Map<String, ExperimentalLanguageSpecificInstrumentationPropertyModel> props, | ||
| String declarativePath, | ||
| String value) { | ||
| String[] segments = declarativePath.split("\\."); | ||
| ExperimentalLanguageSpecificInstrumentationPropertyModel propertyModel = | ||
| props.computeIfAbsent( | ||
| segments[0], key -> new ExperimentalLanguageSpecificInstrumentationPropertyModel()); | ||
| Map<String, Object> target = propertyModel.getAdditionalProperties(); | ||
| for (int i = 1; i < segments.length - 1; i++) { | ||
| Object child = target.get(segments[i]); | ||
| if (child == null) { | ||
| Map<String, Object> nested = new HashMap<>(); | ||
| target.put(segments[i], nested); | ||
| target = nested; | ||
| continue; | ||
| } | ||
| if (!(child instanceof Map)) { | ||
| return; | ||
| } | ||
| // Nested defaults only create string-keyed maps, so this cast is safe here. | ||
| @SuppressWarnings("unchecked") | ||
| Map<String, Object> nested = (Map<String, Object>) child; | ||
| target = nested; | ||
| } | ||
| target.putIfAbsent(segments[segments.length - 1], value); | ||
| } | ||
| } |
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.