Skip to content

declarative config: add declaravite config bridge, gcp auth, span stacktrace #2020

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

Draft
wants to merge 41 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
51cd517
gcp auth
zeitlinger Jul 11, 2025
4a6bda9
./gradlew spotlessApply
otelbot[bot] Jul 11, 2025
d6f03ab
copy declarative config bridge from instrumentation
zeitlinger Jul 14, 2025
1dee070
support config bridge
zeitlinger Jul 14, 2025
a2c30d7
support config bridge
zeitlinger Jul 14, 2025
7564023
copy declarative config bridge from instrumentation
zeitlinger Jul 14, 2025
858d71f
format
zeitlinger Jul 14, 2025
3296368
cleanup
zeitlinger Jul 14, 2025
b268964
cleanup
zeitlinger Jul 14, 2025
6054d35
fix test
zeitlinger Jul 14, 2025
3c15bff
fix
zeitlinger Jul 14, 2025
a5fe6a2
add property translation for inferred spans
zeitlinger Jul 14, 2025
7c48794
inferred spans
zeitlinger Jul 15, 2025
2854093
inferred spans
zeitlinger Jul 15, 2025
0eb9b1e
inferred spans
zeitlinger Jul 15, 2025
1c22094
baggage processor
zeitlinger Jul 15, 2025
d2279bc
format
zeitlinger Jul 15, 2025
63d7c8c
format
zeitlinger Jul 15, 2025
402d305
stack trace span processor
zeitlinger Jul 15, 2025
e5355f5
stack trace span processor
zeitlinger Jul 15, 2025
c6d9eee
fix
zeitlinger Jul 15, 2025
67adb3b
fix
zeitlinger Jul 15, 2025
6daffa5
fix
zeitlinger Jul 15, 2025
434fb80
make temp dir more reliable
zeitlinger Jul 15, 2025
9bc8664
make temp dir more reliable
zeitlinger Jul 15, 2025
d7d4915
revert inferred spans (flaky)
zeitlinger Jul 15, 2025
25c42bf
baggage is in a separate PR
zeitlinger Jul 16, 2025
1aa789c
use unified bridge
zeitlinger Jul 18, 2025
7bf95df
add experimental- suffix, add test
zeitlinger Jul 22, 2025
06cb016
add experimental- suffix, add test
zeitlinger Jul 22, 2025
fedec51
update bridge to match agent
zeitlinger Jul 24, 2025
93b93e1
update bridge to match agent
zeitlinger Jul 24, 2025
cdbe87b
Revert "update bridge to match agent"
zeitlinger Jul 24, 2025
e18a052
update bridge to match agent
zeitlinger Jul 24, 2025
933fb80
Update gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/…
zeitlinger Aug 4, 2025
df08db5
Update gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/…
zeitlinger Aug 4, 2025
bbc3bfb
pr review
zeitlinger Aug 4, 2025
187b278
pr review
zeitlinger Aug 4, 2025
c5c8e86
pr review
zeitlinger Aug 4, 2025
682fc65
update config bridge from agent
zeitlinger Aug 15, 2025
f895a1e
./gradlew spotlessApply
otelbot[bot] Aug 15, 2025
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
18 changes: 18 additions & 0 deletions declarative-config-bridge/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
plugins {
id("otel.java-conventions")
id("otel.publish-conventions")
}

description = "OpenTelemetry extension that provides a bridge for declarative configuration."
otelJava.moduleName.set("io.opentelemetry.contrib.sdk.declarative.config.bridge")

dependencies {
// We use `compileOnly` dependency because during runtime all necessary classes are provided by
// javaagent itself.
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-incubator")

testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
testImplementation("io.opentelemetry:opentelemetry-sdk-extension-incubator")
testImplementation("org.mockito:mockito-inline")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.sdk.autoconfigure;

public final class ConfigPropertiesUtil {
private ConfigPropertiesUtil() {}

public static String propertyYamlPath(String propertyName) {
return yamlPath(propertyName);
}

static String yamlPath(String property) {
String[] segments = DeclarativeConfigPropertiesBridge.getSegments(property);
if (segments.length == 0) {
throw new IllegalArgumentException("Invalid property: " + property);
}

return "'instrumentation/development' / 'java' / '" + String.join("' / '", segments) + "'";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.sdk.autoconfigure;

import static io.opentelemetry.api.incubator.config.DeclarativeConfigProperties.empty;

import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import java.time.Duration;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;
import javax.annotation.Nullable;

/**
* A {@link ConfigProperties} which resolves properties based on {@link
* DeclarativeConfigProperties}.
*
* <p>Only properties starting with "otel.instrumentation." are resolved. Others return null (or
* default value if provided).
*
* <p>To resolve:
*
* <ul>
* <li>"otel.instrumentation" refers to the ".instrumentation.java" node
* <li>The portion of the property after "otel.instrumentation." is split into segments based on
* ".".
* <li>For each N-1 segment, we walk down the tree to find the relevant leaf {@link
* DeclarativeConfigProperties}.
* <li>We extract the property from the resolved {@link DeclarativeConfigProperties} using the
* last segment as the property key.
* </ul>
*
* <p>For example, given the following YAML, asking for {@code
* ConfigProperties#getString("otel.instrumentation.common.string_key")} yields "value":
*
* <pre>
* instrumentation:
* java:
* common:
* string_key: value
* </pre>
*/
final class DeclarativeConfigPropertiesBridge implements ConfigProperties {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the bridge is also part of open-telemetry/opentelemetry-java-instrumentation#14184 - once it's reviewed there it will be updated here

but that can also be done after this PR is merged


private static final String OTEL_INSTRUMENTATION_PREFIX = "otel.instrumentation.";

private final DeclarativeConfigProperties baseNode;

// lookup order matters - we choose the first match
private final Map<String, String> mappings;
private final Map<String, Object> overrideValues;

DeclarativeConfigPropertiesBridge(
DeclarativeConfigProperties baseNode,
Map<String, String> mappings,
Map<String, Object> overrideValues) {
this.baseNode = Objects.requireNonNull(baseNode);
this.mappings = mappings;
this.overrideValues = overrideValues;
}

@Nullable
@Override
public String getString(String propertyName) {
return getPropertyValue(propertyName, String.class, DeclarativeConfigProperties::getString);
}

@Nullable
@Override
public Boolean getBoolean(String propertyName) {
return getPropertyValue(propertyName, Boolean.class, DeclarativeConfigProperties::getBoolean);
}

@Nullable
@Override
public Integer getInt(String propertyName) {
return getPropertyValue(propertyName, Integer.class, DeclarativeConfigProperties::getInt);
}

@Nullable
@Override
public Long getLong(String propertyName) {
return getPropertyValue(propertyName, Long.class, DeclarativeConfigProperties::getLong);
}

@Nullable
@Override
public Double getDouble(String propertyName) {
return getPropertyValue(propertyName, Double.class, DeclarativeConfigProperties::getDouble);
}

@Nullable
@Override
public Duration getDuration(String propertyName) {
Long millis = getPropertyValue(propertyName, Long.class, DeclarativeConfigProperties::getLong);
if (millis == null) {
return null;
}
return Duration.ofMillis(millis);
}

@SuppressWarnings("unchecked")
@Override
public List<String> getList(String propertyName) {
List<String> propertyValue =
getPropertyValue(
propertyName,
List.class,
(properties, lastPart) -> properties.getScalarList(lastPart, String.class));
return propertyValue == null ? Collections.emptyList() : propertyValue;
}

@SuppressWarnings("unchecked")
@Override
public Map<String, String> getMap(String propertyName) {
DeclarativeConfigProperties propertyValue =
getPropertyValue(
propertyName,
DeclarativeConfigProperties.class,
DeclarativeConfigProperties::getStructured);
if (propertyValue == null) {
return Collections.emptyMap();
}
Map<String, String> result = new HashMap<>();
propertyValue
.getPropertyKeys()
.forEach(
key -> {
String value = propertyValue.getString(key);
if (value == null) {
return;
}
result.put(key, value);
});
return Collections.unmodifiableMap(result);
}

@Nullable
private <T> T getPropertyValue(
String property,
Class<T> clazz,
BiFunction<DeclarativeConfigProperties, String, T> extractor) {
T override = clazz.cast(overrideValues.get(property));
if (override != null) {
return override;
}

String[] segments = getSegments(translateProperty(property));
if (segments.length == 0) {
return null;
}

// Extract the value by walking to the N-1 entry
DeclarativeConfigProperties target = baseNode;
if (segments.length > 1) {
for (int i = 0; i < segments.length - 1; i++) {
target = target.getStructured(segments[i], empty());
}
}
String lastPart = segments[segments.length - 1];

return extractor.apply(target, lastPart);
}

static String[] getSegments(String property) {
if (property.startsWith(OTEL_INSTRUMENTATION_PREFIX)) {
property = property.substring(OTEL_INSTRUMENTATION_PREFIX.length());
}
// Split the remainder of the property on "."
return property.replace('-', '_').split("\\.");
}

private String translateProperty(String property) {
for (Map.Entry<String, String> entry : mappings.entrySet()) {
if (property.startsWith(entry.getKey())) {
return entry.getValue() + property.substring(entry.getKey().length());
}
}
return property;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.sdk.autoconfigure;

import static io.opentelemetry.api.incubator.config.DeclarativeConfigProperties.empty;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.opentelemetry.api.incubator.config.ConfigProvider;
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.annotation.Nullable;

/**
* A builder for {@link DeclarativeConfigPropertiesBridge} that allows adding translations and fixed
* values for properties.
*/
public class DeclarativeConfigPropertiesBridgeBuilder {
/**
* order is important here, so we use LinkedHashMap - see {@link #addMapping(String, String)} for
* more details
*/
private final Map<String, String> mappings = new LinkedHashMap<>();

private final Map<String, Object> overrideValues = new HashMap<>();

public DeclarativeConfigPropertiesBridgeBuilder() {}

/**
* Adds a mapping from a property prefix to a YAML path.
*
* <p>For example, if the property prefix is "otel.javaagent" and the YAML path is "agent", then
* any property starting with "otel.javaagent." will be resolved against the "agent" node in the
* instrumentation/java section of the YAML configuration.
*
* @param propertyPrefix the prefix of the property to translate
* @param yamlPath the YAML path to resolve the property against
*/
@CanIgnoreReturnValue
public DeclarativeConfigPropertiesBridgeBuilder addMapping(
String propertyPrefix, String yamlPath) {
mappings.put(propertyPrefix, yamlPath);
return this;
}

/**
* Adds a fixed override value for a property.
*
* @param propertyName the name of the property to override
* @param value the value to return when the property is requested
*/
@CanIgnoreReturnValue
public DeclarativeConfigPropertiesBridgeBuilder addOverride(String propertyName, Object value) {
overrideValues.put(propertyName, value);
return this;
}

/** Build {@link ConfigProperties} from the {@code autoConfiguredOpenTelemetrySdk}. */
public ConfigProperties build(AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) {
ConfigProperties sdkConfigProperties =
AutoConfigureUtil.getConfig(autoConfiguredOpenTelemetrySdk);
if (sdkConfigProperties != null) {
return sdkConfigProperties;
}
ConfigProvider configProvider =
AutoConfigureUtil.getConfigProvider(autoConfiguredOpenTelemetrySdk);
if (configProvider != null) {
return buildFromInstrumentationConfig(configProvider.getInstrumentationConfig());
}
// Should never happen
throw new IllegalStateException(
"AutoConfiguredOpenTelemetrySdk does not have ConfigProperties or DeclarativeConfigProperties. This is likely a programming error in opentelemetry-java");
}

/**
* Build {@link ConfigProperties} from the {@link DeclarativeConfigProperties} provided by the
* instrumentation configuration.
*
* <p>If the provided {@code instrumentationConfig} is null, an empty {@link
* DeclarativeConfigProperties} will be used.
*
* @param instrumentationConfig the instrumentation configuration to build from
* @return a new instance of {@link ConfigProperties}
*/
public ConfigProperties buildFromInstrumentationConfig(
@Nullable DeclarativeConfigProperties instrumentationConfig) {
// leave the name "build" for a future method that builds from a DeclarativeConfigProperties
// instance that doesn't come from the top-level instrumentation config
if (instrumentationConfig == null) {
instrumentationConfig = DeclarativeConfigProperties.empty();
}
return new DeclarativeConfigPropertiesBridge(
instrumentationConfig.getStructured("java", empty()), mappings, overrideValues);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.sdk.autoconfigure;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;

class ConfigPropertiesUtilTest {
@Test
void propertyYamlPath() {
assertThat(ConfigPropertiesUtil.propertyYamlPath("google.otel.auth.target.signals"))
.isEqualTo(
"'instrumentation/development' / 'java' / 'google' / 'otel' / 'auth' / 'target' / 'signals'");
}
}
Loading
Loading