Skip to content

declarative config: early init and property mapping #14184

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
wants to merge 34 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
f8cdbe6
use _ instead of - in declarative config
zeitlinger Jul 4, 2025
52893c8
map "common-enabled"
zeitlinger Jul 4, 2025
1ad5e8a
use yaml file
zeitlinger Jul 4, 2025
c253f13
agent properties, part 1
zeitlinger Jul 4, 2025
ecaadf7
early init config
zeitlinger Jul 4, 2025
69fc94f
early init config
zeitlinger Jul 4, 2025
8fc9067
don't pass model to create the SDK - we need to parse the file twice …
zeitlinger Jul 4, 2025
a98546d
map agent enabled
zeitlinger Jul 4, 2025
2a65f94
map agent props
zeitlinger Jul 4, 2025
41b674b
map agent props
zeitlinger Jul 4, 2025
f7b4fda
fix
zeitlinger Jul 4, 2025
813e53c
fix
zeitlinger Jul 4, 2025
bc0febe
fix
zeitlinger Jul 4, 2025
4ead4e2
fix
zeitlinger Jul 5, 2025
53f132c
map general props
zeitlinger Jul 5, 2025
304289a
map agent props
zeitlinger Jul 5, 2025
40fb7a6
map agent props
zeitlinger Jul 6, 2025
1050615
create sdk from previously read config model
zeitlinger Jul 6, 2025
7c7b1d5
create sdk from previously read config model
zeitlinger Jul 6, 2025
0fbc4d4
map otel.javaagent.debug
zeitlinger Jul 6, 2025
44512ec
format
zeitlinger Jul 6, 2025
db00f64
format
zeitlinger Jul 6, 2025
c46cc0d
set test exporters
zeitlinger Jul 6, 2025
6a3eb14
use extension classloader
zeitlinger Jul 6, 2025
5ee22ce
format
zeitlinger Jul 7, 2025
156865c
inline method
zeitlinger Jul 7, 2025
91cb16f
set global config provider
zeitlinger Jul 7, 2025
a36770e
add more tests
zeitlinger Jul 7, 2025
10e3108
cleanup
zeitlinger Jul 8, 2025
c4562fc
buffer errors
zeitlinger Jul 8, 2025
eb8ee19
avoid reflection
zeitlinger Jul 8, 2025
c370544
avoid reflection
zeitlinger Jul 8, 2025
e4e0b67
format
zeitlinger Jul 8, 2025
1ec8ff4
buffer errors
zeitlinger Jul 8, 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
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import io.opentelemetry.instrumentation.jmx.yaml.RuleParser;
import io.opentelemetry.javaagent.extension.AgentListener;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import java.io.InputStream;
import java.nio.file.Files;
Expand All @@ -28,7 +29,7 @@ public class JmxMetricInsightInstaller implements AgentListener {

@Override
public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) {
ConfigProperties config = AgentListener.resolveConfigProperties(autoConfiguredSdk);
ConfigProperties config = AutoConfigureUtil.getConfig(autoConfiguredSdk);

if (config.getBoolean("otel.jmx.enabled", true)) {
JmxMetricInsight service =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.AgentListener;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import java.lang.reflect.Method;

Expand All @@ -20,7 +21,7 @@ public class OshiMetricsInstaller implements AgentListener {

@Override
public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) {
ConfigProperties config = AgentListener.resolveConfigProperties(autoConfiguredSdk);
ConfigProperties config = AutoConfigureUtil.getConfig(autoConfiguredSdk);

boolean defaultEnabled = config.getBoolean("otel.instrumentation.common.default-enabled", true);
if (!config.getBoolean("otel.instrumentation.oshi.enabled", defaultEnabled)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.bootstrap.InstrumentationHolder;
import io.opentelemetry.javaagent.extension.AgentListener;
import io.opentelemetry.javaagent.tooling.BeforeAgentListener;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import java.lang.instrument.Instrumentation;

Expand All @@ -19,7 +19,7 @@ public class JarAnalyzerInstaller implements BeforeAgentListener {

@Override
public void beforeAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) {
ConfigProperties config = AgentListener.resolveConfigProperties(autoConfiguredOpenTelemetrySdk);
ConfigProperties config = AutoConfigureUtil.getConfig(autoConfiguredOpenTelemetrySdk);

boolean enabled =
config.getBoolean("otel.instrumentation.runtime-telemetry.package-emitter.enabled", false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@

package io.opentelemetry.javaagent.extension;

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 io.opentelemetry.sdk.autoconfigure.spi.Ordered;
import java.lang.instrument.Instrumentation;
import net.bytebuddy.agent.builder.AgentBuilder;
Expand All @@ -29,26 +25,4 @@ public interface AgentListener extends Ordered {
* on an {@link Instrumentation}.
*/
void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk);

/** Resolve {@link ConfigProperties} from the {@code autoConfiguredOpenTelemetrySdk}. */
static ConfigProperties resolveConfigProperties(
AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) {
ConfigProperties sdkConfigProperties =
AutoConfigureUtil.getConfig(autoConfiguredOpenTelemetrySdk);
if (sdkConfigProperties != null) {
return sdkConfigProperties;
}
ConfigProvider configProvider =
AutoConfigureUtil.getConfigProvider(autoConfiguredOpenTelemetrySdk);
if (configProvider != null) {
DeclarativeConfigProperties instrumentationConfig = configProvider.getInstrumentationConfig();

if (instrumentationConfig != null) {
return new DeclarativeConfigPropertiesBridge(instrumentationConfig);
}
}
// Should never happen
throw new IllegalStateException(
"AutoConfiguredOpenTelemetrySdk does not have ConfigProperties or DeclarativeConfigProperties. This is likely a programming error in opentelemetry-java");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,21 @@

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

import io.opentelemetry.api.incubator.config.ConfigProvider;
import io.opentelemetry.api.incubator.config.DeclarativeConfigException;
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import javax.annotation.Nullable;

/**
Expand Down Expand Up @@ -46,26 +53,87 @@
* string_key: value
* </pre>
*/
final class DeclarativeConfigPropertiesBridge implements ConfigProperties {
public final class DeclarativeConfigPropertiesBridge implements ConfigProperties {

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

private static final Map<String, String> JAVA_MAPPING_RULES = new HashMap<>();
private static final Map<String, String> GENERAL_MAPPING_RULES = new HashMap<>();
private static final Set<String> AGENT_LOGGING_OUTPUTS =
new HashSet<>(Arrays.asList("application", "simple"));

// The node at .instrumentation.java
private final DeclarativeConfigProperties instrumentationJavaNode;

DeclarativeConfigPropertiesBridge(DeclarativeConfigProperties instrumentationNode) {
instrumentationJavaNode = instrumentationNode.getStructured("java", empty());
private final DeclarativeConfigProperties instrumentationGeneralNode;

static {
JAVA_MAPPING_RULES.put("otel.instrumentation.common.default-enabled", "common.default.enabled");
JAVA_MAPPING_RULES.put(
"otel.javaagent.logging.application.logs-buffer-max-records",
"agent.logging.output.application.logs_buffer_max_records");

// todo not supported in SDK yet (this is strictly typed)
Copy link
Member Author

Choose a reason for hiding this comment

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

// GENERAL_MAPPING_RULES.put("otel.instrumentation.http.known-methods",
// "http.known_methods");
GENERAL_MAPPING_RULES.put(
"otel.instrumentation.http.client.capture-request-headers",
"http.client.request_captured_headers");
GENERAL_MAPPING_RULES.put(
"otel.instrumentation.http.client.capture-response-headers",
"http.client.response_captured_headers");
GENERAL_MAPPING_RULES.put(
"otel.instrumentation.http.server.capture-request-headers",
"http.server.request_captured_headers");
GENERAL_MAPPING_RULES.put(
"otel.instrumentation.http.server.capture-response-headers",
"http.server.response_captured_headers");
}

private final String logLevel;

private static Map<String, String> getPeerServiceMapping(
DeclarativeConfigPropertiesBridge bridge) {
List<DeclarativeConfigProperties> configProperties =
bridge
.instrumentationGeneralNode
.getStructured("peer", empty())
.getStructuredList("service_mapping", Collections.emptyList());
return configProperties.stream()
.collect(
Collectors.toMap(
e -> Objects.requireNonNull(e.getString("peer"), "peer must not be null"),
e -> Objects.requireNonNull(e.getString("service"), "service must not be null")));
}

public DeclarativeConfigPropertiesBridge(ConfigProvider configProvider, String logLevel) {
this.logLevel = logLevel;
DeclarativeConfigProperties inst = configProvider.getInstrumentationConfig();
if (inst == null) {
inst = DeclarativeConfigProperties.empty();
}
instrumentationJavaNode = inst.getStructured("java", empty());
instrumentationGeneralNode = inst.getStructured("general", empty());
}

@Nullable
@Override
public String getString(String propertyName) {
if ("otel.javaagent.logging".equals(propertyName)) {
return agentLoggerName();
}

return getPropertyValue(propertyName, DeclarativeConfigProperties::getString);
}

@Nullable
@Override
public Boolean getBoolean(String propertyName) {
if ("otel.javaagent.debug".equals(propertyName)) {
return "DEBUG".equals(this.logLevel);
}

return getPropertyValue(propertyName, DeclarativeConfigProperties::getBoolean);
}

Expand Down Expand Up @@ -108,6 +176,10 @@ public List<String> getList(String propertyName) {

@Override
public Map<String, String> getMap(String propertyName) {
if ("otel.instrumentation.common.peer-service-mapping".equals(propertyName)) {
return getPeerServiceMapping(this);
}

DeclarativeConfigProperties propertyValue =
getPropertyValue(propertyName, DeclarativeConfigProperties::getStructured);
if (propertyValue == null) {
Expand All @@ -130,16 +202,26 @@ public Map<String, String> getMap(String propertyName) {
@Nullable
private <T> T getPropertyValue(
String property, BiFunction<DeclarativeConfigProperties, String, T> extractor) {
if (!property.startsWith(OTEL_INSTRUMENTATION_PREFIX)) {
return null;
String generalPath = GENERAL_MAPPING_RULES.get(property);
if (generalPath != null) {
return splitOnDot(generalPath, instrumentationGeneralNode, extractor);
}
String suffix = property.substring(OTEL_INSTRUMENTATION_PREFIX.length());
String javaPath = getJavaPath(property);
if (javaPath != null) {
return splitOnDot(javaPath, instrumentationJavaNode, extractor);
}
return null;
}

private static <T> T splitOnDot(
String path,
DeclarativeConfigProperties target,
BiFunction<DeclarativeConfigProperties, String, T> extractor) {
// Split the remainder of the property on ".", and walk to the N-1 entry
String[] segments = suffix.split("\\.");
String[] segments = path.split("\\.");
if (segments.length == 0) {
return null;
}
DeclarativeConfigProperties target = instrumentationJavaNode;
if (segments.length > 1) {
for (int i = 0; i < segments.length - 1; i++) {
target = target.getStructured(segments[i], empty());
Expand All @@ -148,4 +230,53 @@ private <T> T getPropertyValue(
String lastPart = segments[segments.length - 1];
return extractor.apply(target, lastPart);
}

private static String getJavaPath(String property) {
String special = JAVA_MAPPING_RULES.get(property);
if (special != null) {
return special;
}

if (property.startsWith(OTEL_INSTRUMENTATION_PREFIX)) {
return property.substring(OTEL_INSTRUMENTATION_PREFIX.length()).replace('-', '_');
} else if (property.startsWith(OTEL_JAVA_AGENT_PREFIX)) {
return "agent." + property.substring(OTEL_JAVA_AGENT_PREFIX.length()).replace('-', '_');
}
return null;
}

private String agentLoggerName() {
DeclarativeConfigProperties logOutput = getLogOutput();
Set<String> names = logOutput.getPropertyKeys();

if (names.isEmpty()) {
// no log output configured, use the default
return "simple";
}

if (names.size() > 1) {
throw new DeclarativeConfigException(
"Multiple log output formats are configured: "
+ String.join(", ", names)
+ ". Please choose one of them.");
}

String name = names.iterator().next();
if (!AGENT_LOGGING_OUTPUTS.contains(name)) {
throw new DeclarativeConfigException(
"Unsupported log output format: '"
+ name
+ "' . Supported formats are: "
+ String.join(", ", AGENT_LOGGING_OUTPUTS));
}
return name;
}

private DeclarativeConfigProperties getLogOutput() {
return getAgent().getStructured("logging", empty()).getStructured("output", empty());
}

private DeclarativeConfigProperties getAgent() {
return instrumentationJavaNode.getStructured("agent", empty());
}
}
Loading
Loading