-
Notifications
You must be signed in to change notification settings - Fork 1k
The one true configuration bridge #15641
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
Changes from 3 commits
1f144e1
c75539f
3580b0d
3baf749
7653f0f
1add1a9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| /* | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package io.opentelemetry.instrumentation.config.bridge; | ||
|
|
||
| import io.opentelemetry.api.incubator.config.ConfigProvider; | ||
| import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; | ||
| import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; | ||
| import javax.annotation.Nullable; | ||
|
|
||
| /** | ||
| * A {@link ConfigProvider} implementation backed by {@link ConfigProperties}. | ||
| * | ||
| * <p>This allows instrumentations to always use {@code ExtendedOpenTelemetry.getConfigProvider()} | ||
| * regardless of whether the user started with system properties or YAML. | ||
| */ | ||
| public final class ConfigPropertiesBackedConfigProvider implements ConfigProvider { | ||
|
|
||
| private final DeclarativeConfigProperties instrumentationConfig; | ||
|
|
||
| public static ConfigProvider create(ConfigProperties configProperties) { | ||
| return new ConfigPropertiesBackedConfigProvider(configProperties); | ||
| } | ||
|
|
||
| private ConfigPropertiesBackedConfigProvider(ConfigProperties configProperties) { | ||
| this.instrumentationConfig = | ||
| ConfigPropertiesBackedDeclarativeConfigProperties.createInstrumentationConfig( | ||
| configProperties); | ||
| } | ||
|
|
||
| @Nullable | ||
| @Override | ||
| public DeclarativeConfigProperties getInstrumentationConfig() { | ||
| return instrumentationConfig; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,207 @@ | ||
| /* | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package io.opentelemetry.instrumentation.config.bridge; | ||
|
|
||
| import static java.util.Collections.emptySet; | ||
|
|
||
| import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; | ||
| import io.opentelemetry.common.ComponentLoader; | ||
| import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; | ||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Set; | ||
| import javax.annotation.Nullable; | ||
|
|
||
| /** | ||
| * Implementation of {@link DeclarativeConfigProperties} backed by {@link ConfigProperties}. | ||
| * | ||
| * <p>It tracks the navigation path and only resolves to system properties at the leaf node when a | ||
| * value is actually requested. | ||
| */ | ||
| public final class ConfigPropertiesBackedDeclarativeConfigProperties | ||
| implements DeclarativeConfigProperties { | ||
|
|
||
| private static final String GENERAL_PEER_SERVICE_MAPPING = "general.peer.service_mapping"; | ||
|
|
||
| private static final Map<String, String> LIST_MAPPINGS; | ||
|
|
||
| static { | ||
| LIST_MAPPINGS = new HashMap<>(); | ||
| LIST_MAPPINGS.put( | ||
| "general.http.client.request_captured_headers", | ||
| "otel.instrumentation.http.client.capture-request-headers"); | ||
| LIST_MAPPINGS.put( | ||
| "general.http.client.response_captured_headers", | ||
| "otel.instrumentation.http.client.capture-response-headers"); | ||
| LIST_MAPPINGS.put( | ||
| "general.http.server.request_captured_headers", | ||
| "otel.instrumentation.http.server.capture-request-headers"); | ||
| LIST_MAPPINGS.put( | ||
| "general.http.server.response_captured_headers", | ||
| "otel.instrumentation.http.server.capture-response-headers"); | ||
| } | ||
|
|
||
| private final ConfigProperties configProperties; | ||
| private final List<String> path; | ||
|
|
||
| public static DeclarativeConfigProperties createInstrumentationConfig( | ||
| ConfigProperties configProperties) { | ||
| return new ConfigPropertiesBackedDeclarativeConfigProperties( | ||
| configProperties, Collections.emptyList()); | ||
| } | ||
|
|
||
| private ConfigPropertiesBackedDeclarativeConfigProperties( | ||
| ConfigProperties configProperties, List<String> path) { | ||
| this.configProperties = configProperties; | ||
| this.path = path; | ||
| } | ||
|
|
||
| @Nullable | ||
| @Override | ||
| public String getString(String name) { | ||
| String fullPath = pathWithName(name); | ||
| return configProperties.getString(toPropertyKey(fullPath)); | ||
| } | ||
|
|
||
| @Nullable | ||
| @Override | ||
| public Boolean getBoolean(String name) { | ||
| String fullPath = pathWithName(name); | ||
| return configProperties.getBoolean(toPropertyKey(fullPath)); | ||
| } | ||
|
|
||
| @Nullable | ||
| @Override | ||
| public Integer getInt(String name) { | ||
| String fullPath = pathWithName(name); | ||
| return configProperties.getInt(toPropertyKey(fullPath)); | ||
| } | ||
|
|
||
| @Nullable | ||
| @Override | ||
| public Long getLong(String name) { | ||
| String fullPath = pathWithName(name); | ||
| return configProperties.getLong(toPropertyKey(fullPath)); | ||
| } | ||
|
|
||
| @Nullable | ||
| @Override | ||
| public Double getDouble(String name) { | ||
| String fullPath = pathWithName(name); | ||
| return configProperties.getDouble(toPropertyKey(fullPath)); | ||
| } | ||
|
|
||
| /** | ||
| * Important: this method should return null if there is no structured child with the given name, | ||
| * but unfortunately that is not implementable on top of ConfigProperties. | ||
| * | ||
| * <p>This will be misleading if anyone is comparing the return value to null. | ||
| */ | ||
zeitlinger marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| @Override | ||
| public DeclarativeConfigProperties getStructured(String name) { | ||
| List<String> newPath = new ArrayList<>(path); | ||
| newPath.add(name); | ||
| return new ConfigPropertiesBackedDeclarativeConfigProperties(configProperties, newPath); | ||
| } | ||
|
|
||
| @Nullable | ||
| @Override | ||
| @SuppressWarnings("unchecked") // Safe because T is known to be String via scalarType check | ||
| public <T> List<T> getScalarList(String name, Class<T> scalarType) { | ||
| if (scalarType != String.class) { | ||
| return null; | ||
| } | ||
| String fullPath = pathWithName(name); | ||
|
|
||
| // Check explicit list mappings first | ||
| String mappedKey = LIST_MAPPINGS.get(fullPath); | ||
| if (mappedKey != null) { | ||
| List<String> list = configProperties.getList(mappedKey); | ||
| if (!list.isEmpty()) { | ||
| return (List<T>) list; | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| // Standard mapping | ||
| List<String> list = configProperties.getList(toPropertyKey(fullPath)); | ||
| if (list.isEmpty()) { | ||
| return null; | ||
| } | ||
| return (List<T>) list; | ||
| } | ||
|
|
||
| @Nullable | ||
| @Override | ||
| public List<DeclarativeConfigProperties> getStructuredList(String name) { | ||
| String fullPath = pathWithName(name); | ||
| if (GENERAL_PEER_SERVICE_MAPPING.equals(fullPath)) { | ||
| return PeerServiceMapping.getList(configProperties); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| @Override | ||
| public Set<String> getPropertyKeys() { | ||
| // this is not supported when using system properties based configuration | ||
| return emptySet(); | ||
| } | ||
|
|
||
| @Override | ||
| public ComponentLoader getComponentLoader() { | ||
| return configProperties.getComponentLoader(); | ||
| } | ||
|
|
||
| private String pathWithName(String name) { | ||
| if (path.isEmpty()) { | ||
| return name; | ||
| } | ||
| return String.join(".", path) + "." + name; | ||
| } | ||
|
|
||
| private static String toPropertyKey(String fullPath) { | ||
|
Contributor
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. Do we need to add some way to customize this to make it usable in vendor distros that could need to map their of configuration properties?
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. In my current PoC, I have a mapper class that some common default properties do declarative config, based on what was specified as system properties: It leaves enough wiggle room for vendor conventions. |
||
| String translatedPath = translatePath(fullPath); | ||
|
|
||
| // Handle agent prefix: java.agent.* → otel.javaagent.* | ||
| if (translatedPath.startsWith("agent.")) { | ||
| return "otel.java" + translatedPath; | ||
| } | ||
|
|
||
| // Handle jmx prefix: java.jmx.* → otel.jmx.* | ||
| if (translatedPath.startsWith("jmx.")) { | ||
| return "otel." + translatedPath; | ||
| } | ||
|
|
||
| // Standard mapping | ||
| return "otel.instrumentation." + translatedPath; | ||
| } | ||
|
|
||
| private static String translatePath(String path) { | ||
| StringBuilder result = new StringBuilder(); | ||
| for (String segment : path.split("\\.")) { | ||
| // Skip "java" segment - it doesn't exist in system properties | ||
| if ("java".equals(segment)) { | ||
| continue; | ||
| } | ||
| if (result.length() > 0) { | ||
| result.append("."); | ||
| } | ||
| result.append(translateName(segment)); | ||
| } | ||
| return result.toString(); | ||
| } | ||
|
|
||
| private static String translateName(String name) { | ||
| if (name.endsWith("/development")) { | ||
| return "experimental." | ||
zeitlinger marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| + name.substring(0, name.length() - "/development".length()).replace('_', '-'); | ||
| } | ||
| return name.replace('_', '-'); | ||
| } | ||
| } | ||
trask marked this conversation as resolved.
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| /* | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package io.opentelemetry.instrumentation.config.bridge; | ||
|
|
||
| import static java.util.Collections.emptySet; | ||
|
|
||
| import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; | ||
| import io.opentelemetry.common.ComponentLoader; | ||
| import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; | ||
| import java.util.ArrayList; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Set; | ||
| import javax.annotation.Nullable; | ||
|
|
||
| final class PeerServiceMapping implements DeclarativeConfigProperties { | ||
|
|
||
| private final Map<String, String> fields; | ||
| private final ComponentLoader componentLoader; | ||
|
|
||
| @Nullable | ||
| static List<DeclarativeConfigProperties> getList(ConfigProperties configProperties) { | ||
| Map<String, String> map = | ||
| configProperties.getMap("otel.instrumentation.common.peer-service-mapping"); | ||
| if (map.isEmpty()) { | ||
| return null; | ||
| } | ||
| List<DeclarativeConfigProperties> result = new ArrayList<>(); | ||
| for (Map.Entry<String, String> entry : map.entrySet()) { | ||
| Map<String, String> fields = new HashMap<>(); | ||
| fields.put("peer", entry.getKey()); | ||
| fields.put("service", entry.getValue()); | ||
| result.add(new PeerServiceMapping(fields, configProperties.getComponentLoader())); | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| private PeerServiceMapping(Map<String, String> fields, ComponentLoader componentLoader) { | ||
| this.fields = fields; | ||
| this.componentLoader = componentLoader; | ||
| } | ||
|
|
||
| @Nullable | ||
| @Override | ||
| public String getString(String name) { | ||
| return fields.get(name); | ||
| } | ||
|
|
||
| @Nullable | ||
| @Override | ||
| public Boolean getBoolean(String name) { | ||
| return null; | ||
| } | ||
|
|
||
| @Nullable | ||
| @Override | ||
| public Integer getInt(String name) { | ||
| return null; | ||
| } | ||
|
|
||
| @Nullable | ||
| @Override | ||
| public Long getLong(String name) { | ||
| return null; | ||
| } | ||
|
|
||
| @Nullable | ||
| @Override | ||
| public Double getDouble(String name) { | ||
| return null; | ||
| } | ||
|
|
||
| @Nullable | ||
| @Override | ||
| public DeclarativeConfigProperties getStructured(String name) { | ||
| return null; | ||
| } | ||
|
|
||
| @Nullable | ||
| @Override | ||
| public <T> List<T> getScalarList(String name, Class<T> scalarType) { | ||
| return null; | ||
| } | ||
|
|
||
| @Nullable | ||
| @Override | ||
| public List<DeclarativeConfigProperties> getStructuredList(String name) { | ||
| return null; | ||
| } | ||
|
|
||
| @Override | ||
| public Set<String> getPropertyKeys() { | ||
| // this is not supported when using system properties based configuration | ||
| return emptySet(); | ||
| } | ||
|
|
||
| @Override | ||
| public ComponentLoader getComponentLoader() { | ||
| return componentLoader; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| /* | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package io.opentelemetry.instrumentation.api.incubator.config.internal; | ||
|
|
||
| import io.opentelemetry.api.OpenTelemetry; | ||
| import io.opentelemetry.api.incubator.ExtendedOpenTelemetry; | ||
| import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; | ||
|
|
||
| /** | ||
| * This class is internal and is hence not for public use. Its APIs are unstable and can change at | ||
| * any time. | ||
| */ | ||
| public final class DeclarativeConfigUtil { | ||
|
|
||
| // this is a temporary convenience until getConfigProvider is available on OpenTelemetry | ||
| public static DeclarativeConfigProperties getStructured( | ||
| OpenTelemetry openTelemetry, String name, DeclarativeConfigProperties defaultValue) { | ||
|
|
||
| if (!(openTelemetry instanceof ExtendedOpenTelemetry)) { | ||
| return defaultValue; | ||
zeitlinger marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| ExtendedOpenTelemetry extendedOpenTelemetry = (ExtendedOpenTelemetry) openTelemetry; | ||
| DeclarativeConfigProperties instrumentationConfig = | ||
| extendedOpenTelemetry.getConfigProvider().getInstrumentationConfig(); | ||
| if (instrumentationConfig == null) { | ||
| return defaultValue; | ||
| } | ||
| return instrumentationConfig.getStructured(name, defaultValue); | ||
| } | ||
|
|
||
| private DeclarativeConfigUtil() {} | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.