Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
*/
@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) {
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Member

Choose a reason for hiding this comment

The 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:

https://github.com/grafana/grafana-opentelemetry-java/blob/c0e5e7f7093fbd341eee29eb9f2342d5a6d1d9b1/custom/src/main/java/com/grafana/extensions/DeclarativeConfigPropertyCustomizer.java#L13

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."
+ name.substring(0, name.length() - "/development".length()).replace('_', '-');
}
return name.replace('_', '-');
}
}
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;
}
ExtendedOpenTelemetry extendedOpenTelemetry = (ExtendedOpenTelemetry) openTelemetry;
DeclarativeConfigProperties instrumentationConfig =
extendedOpenTelemetry.getConfigProvider().getInstrumentationConfig();
if (instrumentationConfig == null) {
return defaultValue;
}
return instrumentationConfig.getStructured(name, defaultValue);
}

private DeclarativeConfigUtil() {}
}
Loading
Loading