Skip to content

Commit

Permalink
Support declarative configuration (#12265)
Browse files Browse the repository at this point in the history
  • Loading branch information
jack-berg authored Oct 16, 2024
1 parent d8eddfc commit 780cdf4
Show file tree
Hide file tree
Showing 11 changed files with 329 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
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 @@ -29,7 +28,7 @@ public class JmxMetricInsightInstaller implements AgentListener {

@Override
public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) {
ConfigProperties config = AutoConfigureUtil.getConfig(autoConfiguredSdk);
ConfigProperties config = AgentListener.resolveConfigProperties(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,7 +8,6 @@
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 @@ -21,7 +20,7 @@ public class OshiMetricsInstaller implements AgentListener {

@Override
public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) {
ConfigProperties config = AutoConfigureUtil.getConfig(autoConfiguredSdk);
ConfigProperties config = AgentListener.resolveConfigProperties(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 @@ -12,7 +12,6 @@
import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetricsBuilder;
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;

/** An {@link AgentListener} that enables runtime metrics during agent startup. */
Expand All @@ -21,7 +20,7 @@ public class Java17RuntimeMetricsInstaller implements AgentListener {

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

OpenTelemetry openTelemetry = GlobalOpenTelemetry.get();
RuntimeMetricsBuilder builder = null;
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,8 @@ public class JarAnalyzerInstaller implements BeforeAgentListener {

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

boolean enabled =
config.getBoolean("otel.instrumentation.runtime-telemetry.package-emitter.enabled", false);
if (!enabled) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.JmxRuntimeMetricsUtil;
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.util.ArrayList;
import java.util.List;
Expand All @@ -30,7 +29,7 @@ public class Java8RuntimeMetricsInstaller implements AgentListener {

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

boolean defaultEnabled = config.getBoolean("otel.instrumentation.common.default-enabled", true);
if (!config.getBoolean("otel.instrumentation.runtime-telemetry.enabled", defaultEnabled)
Expand Down
2 changes: 2 additions & 0 deletions javaagent-extension-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ dependencies {
// autoconfigure is unstable, do not expose as api
implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")

testImplementation("io.opentelemetry:opentelemetry-sdk-extension-incubator")

// Used by byte-buddy but not brought in as a transitive dependency.
compileOnly("com.google.code.findbugs:annotations")
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
package io.opentelemetry.javaagent.extension;

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 io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties;
import java.lang.instrument.Instrumentation;
import net.bytebuddy.agent.builder.AgentBuilder;

Expand All @@ -25,4 +28,22 @@ 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;
}
StructuredConfigProperties structuredConfigProperties =
AutoConfigureUtil.getStructuredConfig(autoConfiguredOpenTelemetrySdk);
if (structuredConfigProperties != null) {
return new StructuredConfigPropertiesBridge(structuredConfigProperties);
}
// Should never happen
throw new IllegalStateException(
"AutoConfiguredOpenTelemetrySdk does not have ConfigProperties or StructuredConfigProperties. This is likely a programming error in opentelemetry-java");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.extension;

import static io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties.empty;

import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties;
import java.time.Duration;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import javax.annotation.Nullable;

/**
* A {@link ConfigProperties} which resolves properties based on {@link StructuredConfigProperties}.
*
* <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
* StructuredConfigProperties}.
* <li>We extract the property from the resolved {@link StructuredConfigProperties} 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 StructuredConfigPropertiesBridge implements ConfigProperties {

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

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

StructuredConfigPropertiesBridge(StructuredConfigProperties rootStructuredConfigProperties) {
instrumentationJavaNode =
rootStructuredConfigProperties
.getStructured("instrumentation", empty())
.getStructured("java", empty());
}

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

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

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

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

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

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

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

@Override
public Map<String, String> getMap(String propertyName) {
StructuredConfigProperties propertyValue =
getPropertyValue(propertyName, StructuredConfigProperties::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, BiFunction<StructuredConfigProperties, String, T> extractor) {
if (!property.startsWith(OTEL_INSTRUMENTATION_PREFIX)) {
return null;
}
String suffix = property.substring(OTEL_INSTRUMENTATION_PREFIX.length());
// Split the remainder of the property on ".", and walk to the N-1 entry
String[] segments = suffix.split("\\.");
if (segments.length == 0) {
return null;
}
StructuredConfigProperties target = instrumentationJavaNode;
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);
}
}
Loading

0 comments on commit 780cdf4

Please sign in to comment.